Compare commits

..

154 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
a93d7a8d3a Merge pull request #6171 from SachinVin/dyn
Bump dynarmic and co.
2022-10-28 18:40:16 +05:30
0fb792d216 bump dynarmic: fix moving imm to fpr 2022-10-27 18:47:52 +05:30
4d684174e0 Fix socket poll and handling in windows (#6166)
* Fix socket poll and handling in windows

* Fix clang

* Add guest timing adjust

* Use platform independent time fetch

* Use proper type in time_point

* Fix ambiguous function call

* Do suggestions

* Take cpu_clock_scale into account in tick adjust
2022-10-27 18:35:49 +05:30
a2daef2985 service/nwm_uds: log instead of assert to prevent crashes during multiplayer in Monster Hunter games (#6161) 2022-10-23 23:10:41 +05:30
e74b2575d7 clang format 2022-10-23 13:19:33 +05:30
ae6b7edc25 gc_adapter.cpp: add missing include 2022-10-23 13:19:33 +05:30
fbe06234b1 Core: Port Exclusive memory impl from yuzu
core\arm\dynarmic\arm_dynarmic.cpp: fix build

core\arm\dynarmic\arm_dynarmic.cpp: Fixes

CPP 20
2022-10-23 13:19:33 +05:30
98d3b9c776 externals\CMakeLists.txt: add fmt before dynarmic 2022-10-23 13:19:32 +05:30
4a590d1fcb xbyak: Correct xbyak include directory
xbyak is intended to be installed in /usr/local/include/xbyak.
Since we desire not to install xbyak before using it, we copy the headers
to the appropriate directory structure and use that instead
Co-authored-by: merry <git@mary.rs>
2022-10-23 13:19:32 +05:30
726964ff20 bump xbyak 2022-10-23 13:19:32 +05:30
e5f30fdbf8 bump dynarmic
bump dynarmic

bump dynarmic

bump dynarmic
2022-10-23 13:19:32 +05:30
9626bdf385 Gate use of custom directories behind a variable (#6157)
previous changes had forced every single user to use custom
directories for NAND and SDMC. Those paths were saved to the
config file and would interact badly with portable builds.
2022-10-22 19:09:47 +05:30
67c4b87184 Add internet permission to android build (#6167) 2022-10-20 07:18:32 +05:30
ea26f46b0d renderer_opengl: Fix flashlight problems in Luigi's Mansion (#6160) 2022-10-16 20:19:29 +05:30
150 changed files with 5748 additions and 3970 deletions

View File

@ -138,13 +138,13 @@ if (NOT ENABLE_GENERIC)
if (MSVC)
detect_architecture("_M_AMD64" x86_64)
detect_architecture("_M_IX86" x86)
detect_architecture("_M_ARM" ARM)
detect_architecture("_M_ARM64" ARM64)
detect_architecture("_M_ARM" arm)
detect_architecture("_M_ARM64" arm64)
else()
detect_architecture("__x86_64__" x86_64)
detect_architecture("__i386__" x86)
detect_architecture("__arm__" ARM)
detect_architecture("__aarch64__" ARM64)
detect_architecture("__arm__" arm)
detect_architecture("__aarch64__" arm64)
endif()
endif()
if (NOT DEFINED ARCHITECTURE)

View File

@ -31,24 +31,27 @@ add_subdirectory(catch2)
# Crypto++
add_subdirectory(cryptopp)
# fmt and Xbyak need to be added before dynarmic
# libfmt
add_subdirectory(fmt)
# Xbyak
if (ARCHITECTURE_x86_64)
add_library(xbyak INTERFACE)
target_include_directories(xbyak SYSTEM INTERFACE ./xbyak/xbyak)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
target_include_directories(xbyak SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
endif()
# Dynarmic
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_ARM64)
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
set(DYNARMIC_TESTS OFF)
set(DYNARMIC_NO_BUNDLED_FMT ON)
set(DYNARMIC_FRONTENDS "A32")
add_subdirectory(dynarmic)
endif()
# libfmt
add_subdirectory(fmt)
# getopt
if (MSVC)
add_subdirectory(getopt)

View File

@ -16,6 +16,7 @@
android:required="false" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

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

@ -154,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);
@ -305,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) {
@ -330,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

@ -170,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>());

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);
@ -204,14 +204,15 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
const std::string default_nand_dir = FileUtil::GetDefaultUserPath(FileUtil::UserPath::NANDDir);
FileUtil::UpdateUserPath(
FileUtil::UserPath::NANDDir,
sdl2_config->GetString("Data Storage", "nand_directory", default_nand_dir));
const std::string default_sdmc_dir = FileUtil::GetDefaultUserPath(FileUtil::UserPath::SDMCDir);
FileUtil::UpdateUserPath(
FileUtil::UserPath::SDMCDir,
sdl2_config->GetString("Data Storage", "sdmc_directory", default_sdmc_dir));
Settings::values.use_custom_storage =
sdl2_config->GetBoolean("Data Storage", "use_custom_storage", false);
if (Settings::values.use_custom_storage) {
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
sdl2_config->GetString("Data Storage", "nand_directory", ""));
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
sdl2_config->GetString("Data Storage", "sdmc_directory", ""));
}
// System
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", true);

View File

@ -250,6 +250,10 @@ volume =
# 1 (default): Yes, 0: No
use_virtual_sd =
# Whether to use custom storage locations
# 1: Yes, 0 (default): No
use_custom_storage =
# The path of the virtual SD card directory.
# empty (default) will use the user_path
sdmc_directory =

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();
@ -304,21 +304,17 @@ void Config::ReadDataStorageValues() {
Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
Settings::values.use_custom_storage =
ReadSetting(QStringLiteral("use_custom_storage"), false).toBool();
const std::string nand_dir =
ReadSetting(
QStringLiteral("nand_directory"),
QString::fromStdString(FileUtil::GetDefaultUserPath(FileUtil::UserPath::NANDDir)))
.toString()
.toStdString();
ReadSetting(QStringLiteral("nand_directory"), QStringLiteral("")).toString().toStdString();
const std::string sdmc_dir =
ReadSetting(
QStringLiteral("sdmc_directory"),
QString::fromStdString(FileUtil::GetDefaultUserPath(FileUtil::UserPath::SDMCDir)))
.toString()
.toStdString();
ReadSetting(QStringLiteral("sdmc_directory"), QStringLiteral("")).toString().toStdString();
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, nand_dir);
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, sdmc_dir);
if (Settings::values.use_custom_storage) {
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir, nand_dir);
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir, sdmc_dir);
}
qt_config->endGroup();
}
@ -489,6 +485,7 @@ void Config::ReadRendererValues() {
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();
@ -876,12 +873,13 @@ void Config::SaveDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
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(FileUtil::GetDefaultUserPath(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(FileUtil::GetDefaultUserPath(FileUtil::UserPath::SDMCDir)));
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)),
QStringLiteral(""));
qt_config->endGroup();
}
@ -1007,6 +1005,7 @@ void Config::SaveRendererValues() {
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__

View File

@ -4,6 +4,7 @@
#include <QDesktopServices>
#include <QUrl>
#include <QMessageBox>
#include "citra_qt/configuration/configure_debug.h"
#include "citra_qt/debugger/console.h"
#include "citra_qt/uisettings.h"
@ -11,7 +12,9 @@
#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>()) {
@ -19,10 +22,40 @@ 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));
});
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);

View File

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

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

@ -26,6 +26,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
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());
@ -83,6 +84,7 @@ void ConfigureGraphics::SetConfiguration() {
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() {
@ -96,6 +98,7 @@ void ConfigureGraphics::ApplyConfiguration() {
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() {

View File

@ -171,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

@ -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();
}
});
@ -51,28 +51,42 @@ ConfigureStorage::ConfigureStorage(QWidget* parent)
ApplyConfiguration();
SetConfiguration();
});
connect(ui->toggle_custom_storage, &QCheckBox::clicked, this, [this]() {
ApplyConfiguration();
SetConfiguration();
});
}
ConfigureStorage::~ConfigureStorage() = default;
void ConfigureStorage::SetConfiguration() {
ui->nand_group->setVisible(Settings::values.use_virtual_sd);
QString nand_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
ui->nand_group->setVisible(Settings::values.use_custom_storage);
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);
QString sdmc_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
ui->sdmc_group->setVisible(Settings::values.use_virtual_sd &&
Settings::values.use_custom_storage);
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());
ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd);
ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage);
ui->storage_group->setEnabled(!Core::System::GetInstance().IsPoweredOn());
}
void ConfigureStorage::ApplyConfiguration() {
Settings::values.use_virtual_sd = ui->toggle_virtual_sd->isChecked();
Settings::values.use_custom_storage = ui->toggle_custom_storage->isChecked();
if (!Settings::values.use_custom_storage) {
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
GetDefaultUserPath(Common::FS::UserPath::NANDDir));
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
GetDefaultUserPath(Common::FS::UserPath::SDMCDir));
}
}
void ConfigureStorage::RetranslateUI() {

View File

@ -34,131 +34,147 @@
</layout>
</item>
<item>
<widget class="QGroupBox" name="nand_group">
<widget class="QGroupBox" name="custom_storage_group">
<property name="title">
<string/>
<string>Custom Storage</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>NAND Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="nand_dir_path">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="open_nand_dir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
<widget class="QCheckBox" name="toggle_custom_storage">
<property name="text">
<string>Use Custom Storage</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>NOTE: This does not move the contents of the previous directory to the new one.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="change_nand_dir">
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="sdmc_group">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>SDMC Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="sdmc_dir_path">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="open_sdmc_dir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
<widget class="QGroupBox" name="nand_group">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>NAND Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="nand_dir_path">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="open_nand_dir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>NOTE: This does not move the contents of the previous directory to the new one.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="change_nand_dir">
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>NOTE: This does not move the contents of the previous directory to the new one.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="change_sdmc_dir">
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
</layout>
<widget class="QGroupBox" name="sdmc_group">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>SDMC Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="sdmc_dir_path">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="open_sdmc_dir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>NOTE: This does not move the contents of the previous directory to the new one.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="change_sdmc_dir">
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>

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} {}
@ -462,6 +465,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
default:
break;
}
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
@ -475,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);
@ -523,22 +535,29 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA);
});
connect(open_texture_dump_location, &QAction::triggered, this, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
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, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
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_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 (FileUtil::CreateFullPath(fmt::format("{}mods/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
if (Common::FS::CreateFullPath(fmt::format("{}mods/{:016X}/",
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
}
@ -548,6 +567,26 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
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) {

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

@ -138,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>());
@ -1324,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;
}
@ -1340,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;
@ -1401,9 +1410,9 @@ 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);
@ -1434,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;
@ -1864,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() {
@ -1964,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;
};
@ -2473,7 +2482,7 @@ int main(int argc, char* argv[]) {
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
#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

@ -58,6 +58,7 @@ add_library(common STATIC
announce_multiplayer_room.h
archives.h
assert.h
atomic_ops.h
detached_tasks.cpp
detached_tasks.h
bit_field.h
@ -68,9 +69,20 @@ 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
@ -128,7 +140,7 @@ if(ARCHITECTURE_x86_64)
x64/xbyak_abi.h
x64/xbyak_util.h
)
elseif(ARCHITECTURE_ARM64)
elseif(ARCHITECTURE_arm64)
target_sources(common
PRIVATE
aarch64/cpu_detect.cpp

166
src/common/atomic_ops.h Normal file
View File

@ -0,0 +1,166 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#if _MSC_VER
#include <intrin.h>
#else
#include <cstring>
#endif
namespace Common {
#if _MSC_VER
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
const u8 result =
_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
return result == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
const u16 result =
_InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
return result == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
const u32 result =
_InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
return result == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer),
value, expected);
return result == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
value[0],
reinterpret_cast<__int64*>(expected.data())) != 0;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected,
u8& actual) {
actual =
_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected,
u16& actual) {
actual =
_InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected,
u32& actual) {
actual =
_InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected,
u64& actual) {
actual = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer), value,
expected);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected,
u128& actual) {
const bool result =
_InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
value[0], reinterpret_cast<__int64*>(expected.data())) != 0;
actual = expected;
return result;
}
[[nodiscard]] inline u128 AtomicLoad128(volatile u64* pointer) {
u128 result{};
_InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), result[1],
result[0], reinterpret_cast<__int64*>(result.data()));
return result;
}
#else
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
unsigned __int128 value_a;
unsigned __int128 expected_a;
std::memcpy(&value_a, value.data(), sizeof(u128));
std::memcpy(&expected_a, expected.data(), sizeof(u128));
return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected,
u8& actual) {
actual = __sync_val_compare_and_swap(pointer, expected, value);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected,
u16& actual) {
actual = __sync_val_compare_and_swap(pointer, expected, value);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected,
u32& actual) {
actual = __sync_val_compare_and_swap(pointer, expected, value);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected,
u64& actual) {
actual = __sync_val_compare_and_swap(pointer, expected, value);
return actual == expected;
}
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected,
u128& actual) {
unsigned __int128 value_a;
unsigned __int128 expected_a;
unsigned __int128 actual_a;
std::memcpy(&value_a, value.data(), sizeof(u128));
std::memcpy(&expected_a, expected.data(), sizeof(u128));
actual_a = __sync_val_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
std::memcpy(actual.data(), &actual_a, sizeof(u128));
return actual_a == expected_a;
}
[[nodiscard]] inline u128 AtomicLoad128(volatile u64* pointer) {
unsigned __int128 zeros_a = 0;
unsigned __int128 result_a =
__sync_val_compare_and_swap((unsigned __int128*)pointer, zeros_a, zeros_a);
u128 result;
std::memcpy(result.data(), &result_a, sizeof(u128));
return result;
}
#endif
} // namespace Common

View File

@ -24,6 +24,7 @@
#pragma once
#include <array>
#include <cstdint>
#ifdef _MSC_VER
@ -50,6 +51,9 @@ typedef double f64; ///< 64-bit floating point
typedef u32 VAddr; ///< Represents a pointer in the userspace virtual address space.
typedef u32 PAddr; ///< Represents a pointer in the ARM11 physical address space.
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
// An inheritable class to disallow the copy constructor and operator= functions
class NonCopyable {
protected:

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

@ -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) {

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

@ -6,7 +6,7 @@
#include <bitset>
#include <initializer_list>
#include <xbyak.h>
#include <xbyak/xbyak.h>
#include "common/assert.h"
namespace Common::X64 {

View File

@ -5,7 +5,7 @@
#pragma once
#include <type_traits>
#include <xbyak.h>
#include <xbyak/xbyak.h>
#include "common/x64/xbyak_abi.h"
namespace Common::X64 {

View File

@ -12,6 +12,8 @@ add_library(core STATIC
arm/dyncom/arm_dyncom_thumb.h
arm/dyncom/arm_dyncom_trans.cpp
arm/dyncom/arm_dyncom_trans.h
arm/exclusive_monitor.cpp
arm/exclusive_monitor.h
arm/skyeye_common/arm_regformat.h
arm/skyeye_common/armstate.cpp
arm/skyeye_common/armstate.h
@ -480,12 +482,14 @@ if (ENABLE_WEB_SERVICE)
endif()
endif()
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_ARM64)
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.cpp
arm/dynarmic/arm_dynarmic.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp
arm/dynarmic/arm_exclusive_monitor.h
)
target_link_libraries(core PRIVATE dynarmic)
endif()

View File

@ -122,6 +122,9 @@ public:
*/
virtual void InvalidateCacheRange(u32 start_address, std::size_t length) = 0;
/// Clears the exclusive monitor's state.
virtual void ClearExclusiveState() = 0;
/// Notify CPU emulation that page tables have changed
virtual void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) = 0;

View File

@ -3,12 +3,14 @@
// Refer to the license.txt file included.
#include <cstring>
#include <dynarmic/A32/a32.h>
#include <dynarmic/A32/context.h>
#include <dynarmic/interface/A32/a32.h>
#include <dynarmic/interface/A32/context.h>
#include <dynarmic/interface/optimization_flags.h>
#include "common/assert.h"
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/gdbstub/gdbstub.h"
@ -100,10 +102,23 @@ public:
memory.Write64(vaddr, value);
}
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
return memory.WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
return memory.WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
return memory.WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
return memory.WriteExclusive64(vaddr, value, expected);
}
void InterpreterFallback(VAddr pc, std::size_t num_instructions) override {
// Should never happen.
UNREACHABLE_MSG("InterpeterFallback reached with pc = 0x{:08x}, code = 0x{:08x}, num = {}",
pc, MemoryReadCode(pc), num_instructions);
pc, MemoryReadCode(pc).value(), num_instructions);
}
void CallSVC(std::uint32_t swi) override {
@ -114,6 +129,8 @@ public:
switch (exception) {
case Dynarmic::A32::Exception::UndefinedInstruction:
case Dynarmic::A32::Exception::UnpredictableInstruction:
case Dynarmic::A32::Exception::DecodeError:
case Dynarmic::A32::Exception::NoExecuteFault:
break;
case Dynarmic::A32::Exception::Breakpoint:
if (GDBStub::IsConnected()) {
@ -130,10 +147,11 @@ public:
case Dynarmic::A32::Exception::Yield:
case Dynarmic::A32::Exception::PreloadData:
case Dynarmic::A32::Exception::PreloadDataWithIntentToWrite:
case Dynarmic::A32::Exception::PreloadInstruction:
return;
}
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", exception,
pc, MemoryReadCode(pc));
pc, MemoryReadCode(pc).value());
}
void AddTicks(std::uint64_t ticks) override {
@ -149,10 +167,12 @@ public:
Memory::MemorySystem& memory;
};
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u32 id,
std::shared_ptr<Core::Timing::Timer> timer)
: ARM_Interface(id, timer), system(*system), memory(memory),
cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
ARM_Dynarmic::ARM_Dynarmic(Core::System* system_, Memory::MemorySystem& memory_, u32 core_id_,
std::shared_ptr<Core::Timing::Timer> timer_,
Core::ExclusiveMonitor& exclusive_monitor_)
: ARM_Interface(core_id_, timer_), system(*system_), memory(memory_),
cb(std::make_unique<DynarmicUserCallbacks>(*this)),
exclusive_monitor{dynamic_cast<Core::DynarmicExclusiveMonitor&>(exclusive_monitor_)} {
SetPageTable(memory.GetCurrentPageTable());
}
@ -208,8 +228,7 @@ u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
default:
UNREACHABLE_MSG("Unknown VFP system register: {}", reg);
}
return 0;
return UINT_MAX;
}
void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
@ -295,6 +314,10 @@ void ARM_Dynarmic::InvalidateCacheRange(u32 start_address, std::size_t length) {
jit->InvalidateCacheRange(start_address, length);
}
void ARM_Dynarmic::ClearExclusiveState() {
jit->ClearExclusiveState();
}
std::shared_ptr<Memory::PageTable> ARM_Dynarmic::GetPageTable() const {
return current_page_table;
}
@ -332,6 +355,11 @@ std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
config.page_table = &current_page_table->GetPointerArray();
config.coprocessors[15] = std::make_shared<DynarmicCP15>(cp15_state);
config.define_unpredictable_behaviour = true;
// Multi-process state
config.processor_id = GetID();
config.global_monitor = &exclusive_monitor.monitor;
return std::make_unique<Dynarmic::A32::Jit>(config);
}

View File

@ -6,7 +6,7 @@
#include <map>
#include <memory>
#include <dynarmic/A32/a32.h>
#include <dynarmic/interface/A32/a32.h>
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
@ -17,15 +17,18 @@ class MemorySystem;
} // namespace Memory
namespace Core {
class DynarmicExclusiveMonitor;
class ExclusiveMonitor;
class System;
}
} // namespace Core
class DynarmicUserCallbacks;
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u32 id,
std::shared_ptr<Core::Timing::Timer> timer);
explicit ARM_Dynarmic(Core::System* system_, Memory::MemorySystem& memory_, u32 core_id_,
std::shared_ptr<Core::Timing::Timer> timer,
Core::ExclusiveMonitor& exclusive_monitor_);
~ARM_Dynarmic() override;
void Run() override;
@ -52,6 +55,7 @@ public:
void ClearInstructionCache() override;
void InvalidateCacheRange(u32 start_address, std::size_t length) override;
void ClearExclusiveState() override;
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
void PurgeState() override;
@ -69,6 +73,7 @@ private:
u32 fpexc = 0;
CP15State cp15_state;
Core::DynarmicExclusiveMonitor& exclusive_monitor;
Dynarmic::A32::Jit* jit = nullptr;
std::shared_ptr<Memory::PageTable> current_page_table = nullptr;

View File

@ -5,7 +5,7 @@
#pragma once
#include <memory>
#include <dynarmic/A32/coprocessor.h>
#include <dynarmic/interface/A32/coprocessor.h>
#include "common/common_types.h"
struct CP15State {

View File

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/memory.h"
namespace Core {
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::MemorySystem& memory_,
std::size_t core_count_)
: monitor{core_count_}, memory{memory_} {}
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
}
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
}
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
}
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
}
void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
monitor.ClearProcessor(core_index);
}
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
return memory.WriteExclusive8(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
return memory.WriteExclusive16(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
return memory.WriteExclusive32(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
return memory.WriteExclusive64(vaddr, value, expected);
});
}
} // namespace Core

View File

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <dynarmic/interface/exclusive_monitor.h>
#include "common/common_types.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/exclusive_monitor.h"
namespace Memory {
class MemorySystem;
}
namespace Core {
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
public:
explicit DynarmicExclusiveMonitor(Memory::MemorySystem& memory_, std::size_t core_count_);
~DynarmicExclusiveMonitor() override;
u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;
u16 ExclusiveRead16(std::size_t core_index, VAddr addr) override;
u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
void ClearExclusive(std::size_t core_index) override;
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
private:
friend class ::ARM_Dynarmic;
Dynarmic::ExclusiveMonitor monitor;
Memory::MemorySystem& memory;
};
} // namespace Core

View File

@ -30,6 +30,7 @@ public:
void ClearInstructionCache() override;
void InvalidateCacheRange(u32 start_address, std::size_t length) override;
void ClearExclusiveState() override{};
void SetPC(u32 pc) override;
u32 GetPC() const override;

View File

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/memory.h"
#include "core/settings.h"
namespace Core {
ExclusiveMonitor::~ExclusiveMonitor() = default;
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::MemorySystem& memory,
std::size_t num_cores) {
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
if (Settings::values.use_cpu_jit) {
return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
}
#endif
// TODO(merry): Passthrough exclusive monitor
return nullptr;
}
} // namespace Core

View File

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "common/common_types.h"
namespace Memory {
class MemorySystem;
}
namespace Core {
class ExclusiveMonitor {
public:
virtual ~ExclusiveMonitor();
virtual u8 ExclusiveRead8(std::size_t core_index, VAddr addr) = 0;
virtual u16 ExclusiveRead16(std::size_t core_index, VAddr addr) = 0;
virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
virtual void ClearExclusive(std::size_t core_index) = 0;
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;
virtual bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) = 0;
virtual bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) = 0;
virtual bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) = 0;
};
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::MemorySystem& memory,
std::size_t num_cores);
} // namespace Core

View File

@ -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

@ -13,7 +13,8 @@
#include "common/logging/log.h"
#include "common/texture.h"
#include "core/arm/arm_interface.h"
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_ARM64)
#include "core/arm/exclusive_monitor.h"
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/dyncom/arm_dyncom.h"
@ -247,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);
@ -313,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) {
@ -364,11 +365,12 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
kernel = std::make_unique<Kernel::KernelSystem>(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode);
exclusive_monitor = MakeExclusiveMonitor(*memory, num_cores);
if (Settings::values.use_cpu_jit) {
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_ARM64)
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
for (u32 i = 0; i < num_cores; ++i) {
cpu_cores.push_back(
std::make_shared<ARM_Dynarmic>(this, *memory, i, timing->GetTimer(i)));
cpu_cores.push_back(std::make_shared<ARM_Dynarmic>(
this, *memory, i, timing->GetTimer(i), *exclusive_monitor));
}
#else
for (u32 i = 0; i < num_cores; ++i) {
@ -541,6 +543,7 @@ void System::Shutdown(bool is_deserializing) {
dsp_core.reset();
kernel.reset();
cpu_cores.clear();
exclusive_monitor.reset();
timing.reset();
if (video_dumper && video_dumper->IsDumping()) {

View File

@ -61,6 +61,7 @@ class RendererBase;
namespace Core {
class ExclusiveMonitor;
class Timing;
class System {
@ -361,6 +362,8 @@ private:
std::unique_ptr<Kernel::KernelSystem> kernel;
std::unique_ptr<Timing> timing;
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
private:
static System s_instance;

View File

@ -174,6 +174,22 @@ void Timing::Timer::MoveEvents() {
}
}
u32 Timing::Timer::StartAdjust() {
ASSERT((adjust_value_curr_handle & 1) == 0); // Should always be even
adjust_value_last = std::chrono::steady_clock::now();
return ++adjust_value_curr_handle;
}
void Timing::Timer::EndAdjust(u32 start_adjust_handle) {
std::chrono::time_point<std::chrono::steady_clock> new_timer = std::chrono::steady_clock::now();
ASSERT(new_timer >= adjust_value_last && start_adjust_handle == adjust_value_curr_handle);
AddTicks(nsToCycles(static_cast<float>(
std::chrono::duration_cast<std::chrono::nanoseconds>(new_timer - adjust_value_last)
.count() /
cpu_clock_scale)));
++adjust_value_curr_handle;
}
s64 Timing::Timer::GetMaxSliceLength() const {
const auto& next_event = event_queue.begin();
if (next_event != event_queue.end()) {

View File

@ -203,6 +203,11 @@ public:
void MoveEvents();
// Use these two functions to adjust the guest system tick on host blocking operations, so
// that the guest can tell how much time passed during the host call.
u32 StartAdjust();
void EndAdjust(u32 start_adjust_handle);
private:
friend class Timing;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
@ -227,6 +232,9 @@ public:
s64 downcount = MAX_SLICE_LENGTH;
s64 executed_ticks = 0;
u64 idled_cycles = 0;
std::chrono::time_point<std::chrono::steady_clock> adjust_value_last;
u32 adjust_value_curr_handle = 0;
// Stores a scaling for the internal clockspeed. Changing this number results in
// under/overclocking the guest cpu
double cpu_clock_scale = 1.0;

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] == '.');

View File

@ -25,9 +25,9 @@ namespace FileSys {
class DiskFile : public FileBackend {
public:
DiskFile(FileUtil::IOFile&& file_, const Mode& mode_,
DiskFile(Common::FS::IOFile&& file_, const Mode& mode_,
std::unique_ptr<DelayGenerator> delay_generator_)
: file(new FileUtil::IOFile(std::move(file_))) {
: file(new Common::FS::IOFile(std::move(file_))) {
delay_generator = std::move(delay_generator_);
mode.hex = mode_.hex;
}
@ -45,7 +45,7 @@ public:
protected:
Mode mode;
std::unique_ptr<FileUtil::IOFile> file;
std::unique_ptr<Common::FS::IOFile> file;
private:
DiskFile() = default;
@ -74,11 +74,11 @@ public:
}
protected:
FileUtil::FSTEntry directory{};
Common::FS::FSTEntry directory{};
// We need to remember the last entry we returned, so a subsequent call to Read will continue
// from the next one. This iterator will always point to the next unread entry.
std::vector<FileUtil::FSTEntry>::iterator children_iterator;
std::vector<Common::FS::FSTEntry>::iterator children_iterator;
private:
DiskDirectory() = default;

View File

@ -140,17 +140,17 @@ std::string LayeredFS::ReadName(u32 offset, u32 name_length) {
}
void LayeredFS::LoadRelocations() {
if (!FileUtil::Exists(patch_path)) {
if (!Common::FS::Exists(patch_path)) {
return;
}
const FileUtil::DirectoryEntryCallable callback = [this,
const Common::FS::DirectoryEntryCallable callback = [this,
&callback](u64* /*num_entries_out*/,
const std::string& directory,
const std::string& virtual_name) {
auto* parent = directory_path_map.at(directory.substr(patch_path.size() - 1));
if (FileUtil::IsDirectory(directory + virtual_name + DIR_SEP)) {
if (Common::FS::IsDirectory(directory + virtual_name + DIR_SEP)) {
const auto path = (directory + virtual_name + DIR_SEP).substr(patch_path.size() - 1);
if (!directory_path_map.count(path)) { // Add this directory
auto directory = std::make_unique<Directory>();
@ -161,7 +161,7 @@ void LayeredFS::LoadRelocations() {
parent->directories.emplace_back(std::move(directory));
LOG_INFO(Service_FS, "LayeredFS created directory {}", path);
}
return FileUtil::ForeachDirectoryEntry(nullptr, directory + virtual_name + DIR_SEP,
return Common::FS::ForeachDirectoryEntry(nullptr, directory + virtual_name + DIR_SEP,
callback);
}
@ -179,16 +179,16 @@ void LayeredFS::LoadRelocations() {
auto* file = file_path_map.at(path);
file->relocation.type = 1;
file->relocation.replace_file_path = directory + virtual_name;
file->relocation.size = FileUtil::GetSize(directory + virtual_name);
file->relocation.size = Common::FS::GetSize(directory + virtual_name);
LOG_INFO(Service_FS, "LayeredFS replacement file in use for {}", path);
return true;
};
FileUtil::ForeachDirectoryEntry(nullptr, patch_path, callback);
Common::FS::ForeachDirectoryEntry(nullptr, patch_path, callback);
}
void LayeredFS::LoadExtRelocations() {
if (!FileUtil::Exists(patch_ext_path)) {
if (!Common::FS::Exists(patch_ext_path)) {
return;
}
@ -197,11 +197,11 @@ void LayeredFS::LoadExtRelocations() {
patch_ext_path.erase(patch_ext_path.size() - 1, 1);
}
FileUtil::FSTEntry result;
FileUtil::ScanDirectoryTree(patch_ext_path, result, 256);
Common::FS::FSTEntry result;
Common::FS::ScanDirectoryTree(patch_ext_path, result, 256);
for (const auto& entry : result.children) {
if (FileUtil::IsDirectory(entry.physicalName)) {
if (Common::FS::IsDirectory(entry.physicalName)) {
continue;
}
@ -230,7 +230,7 @@ void LayeredFS::LoadExtRelocations() {
continue;
}
FileUtil::IOFile patch_file(entry.physicalName, "rb");
Common::FS::IOFile patch_file(entry.physicalName, "rb");
if (!patch_file) {
LOG_ERROR(Service_FS, "LayeredFS Could not open file {}", entry.physicalName);
continue;
@ -522,7 +522,7 @@ std::size_t LayeredFS::ReadFile(std::size_t offset, std::size_t length, u8* buff
romfs->ReadFile(relocation.original_offset + relative_offset, to_read,
buffer + read_size);
} else if (relocation.type == 1) { // replace
FileUtil::IOFile replace_file(relocation.replace_file_path, "rb");
Common::FS::IOFile replace_file(relocation.replace_file_path, "rb");
if (replace_file) {
replace_file.Seek(relative_offset, SEEK_SET);
replace_file.ReadBytes(buffer + read_size, to_read);
@ -548,7 +548,7 @@ std::size_t LayeredFS::ReadFile(std::size_t offset, std::size_t length, u8* buff
}
bool LayeredFS::ExtractDirectory(Directory& current, const std::string& target_path) {
if (!FileUtil::CreateFullPath(target_path + current.path)) {
if (!Common::FS::CreateFullPath(target_path + current.path)) {
LOG_ERROR(Service_FS, "Could not create path {}", target_path + current.path);
return false;
}
@ -560,7 +560,7 @@ bool LayeredFS::ExtractDirectory(Directory& current, const std::string& target_p
const auto path = target_path + file->path;
LOG_INFO(Service_FS, "Extracting {} to {}", file->path, path);
FileUtil::IOFile target_file(path, "wb");
Common::FS::IOFile target_file(path, "wb");
if (!target_file) {
LOG_ERROR(Service_FS, "Could not open file {}", path);
return false;

View File

@ -116,7 +116,7 @@ static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decom
NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition)
: ncch_offset(ncch_offset), partition(partition), filepath(filepath) {
file = FileUtil::IOFile(filepath, "rb");
file = Common::FS::IOFile(filepath, "rb");
}
Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 ncch_offset,
@ -124,7 +124,7 @@ Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 nc
this->filepath = filepath;
this->ncch_offset = ncch_offset;
this->partition = partition;
file = FileUtil::IOFile(filepath, "rb");
file = Common::FS::IOFile(filepath, "rb");
if (!file.IsOpen()) {
LOG_WARNING(Service_FS, "Failed to open {}", filepath);
@ -330,7 +330,7 @@ Loader::ResultStatus NCCHContainer::Load() {
// System archives and DLC don't have an extended header but have RomFS
if (ncch_header.extended_header_size) {
auto read_exheader = [this](FileUtil::IOFile& file) {
auto read_exheader = [this](Common::FS::IOFile& file) {
const std::size_t size = sizeof(exheader_header);
return file && file.ReadBytes(&exheader_header, size) == size;
};
@ -360,7 +360,7 @@ Loader::ResultStatus NCCHContainer::Load() {
}
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
fmt::format("{}mods/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<std::string, 2> exheader_override_paths{{
mods_path + "exheader.bin",
@ -369,7 +369,7 @@ Loader::ResultStatus NCCHContainer::Load() {
bool has_exheader_override = false;
for (const auto& path : exheader_override_paths) {
FileUtil::IOFile exheader_override_file{path, "rb"};
Common::FS::IOFile exheader_override_file{path, "rb"};
if (read_exheader(exheader_override_file)) {
has_exheader_override = true;
break;
@ -430,7 +430,7 @@ Loader::ResultStatus NCCHContainer::Load() {
.ProcessData(data, data, sizeof(exefs_header));
}
exefs_file = FileUtil::IOFile(filepath, "rb");
exefs_file = Common::FS::IOFile(filepath, "rb");
has_exefs = true;
}
@ -451,15 +451,15 @@ Loader::ResultStatus NCCHContainer::Load() {
Loader::ResultStatus NCCHContainer::LoadOverrides() {
// Check for split-off files, mark the archive as tainted if we will use them
std::string romfs_override = filepath + ".romfs";
if (FileUtil::Exists(romfs_override)) {
if (Common::FS::Exists(romfs_override)) {
is_tainted = true;
}
// If we have a split-off exefs file/folder, it takes priority
std::string exefs_override = filepath + ".exefs";
std::string exefsdir_override = filepath + ".exefsdir/";
if (FileUtil::Exists(exefs_override)) {
exefs_file = FileUtil::IOFile(exefs_override, "rb");
if (Common::FS::Exists(exefs_override)) {
exefs_file = Common::FS::IOFile(exefs_override, "rb");
if (exefs_file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) == sizeof(ExeFs_Header)) {
LOG_DEBUG(Service_FS, "Loading ExeFS section from {}", exefs_override);
@ -467,9 +467,9 @@ Loader::ResultStatus NCCHContainer::LoadOverrides() {
is_tainted = true;
has_exefs = true;
} else {
exefs_file = FileUtil::IOFile(filepath, "rb");
exefs_file = Common::FS::IOFile(filepath, "rb");
}
} else if (FileUtil::Exists(exefsdir_override) && FileUtil::IsDirectory(exefsdir_override)) {
} else if (Common::FS::Exists(exefsdir_override) && Common::FS::IsDirectory(exefsdir_override)) {
is_tainted = true;
}
@ -585,7 +585,7 @@ Loader::ResultStatus NCCHContainer::ApplyCodePatch(std::vector<u8>& code) const
};
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
fmt::format("{}mods/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<PatchLocation, 6> patch_paths{{
{mods_path + "exefs/code.ips", Patch::ApplyIpsPatch},
@ -597,7 +597,7 @@ Loader::ResultStatus NCCHContainer::ApplyCodePatch(std::vector<u8>& code) const
}};
for (const PatchLocation& info : patch_paths) {
FileUtil::IOFile file{info.path, "rb"};
Common::FS::IOFile file{info.path, "rb"};
if (!file)
continue;
@ -631,7 +631,7 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
return Loader::ResultStatus::Error;
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
fmt::format("{}mods/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<std::string, 3> override_paths{{
mods_path + "exefs/" + override_name,
@ -640,7 +640,7 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
}};
for (const auto& path : override_paths) {
FileUtil::IOFile section_file(path, "rb");
Common::FS::IOFile section_file(path, "rb");
if (section_file.IsOpen()) {
auto section_size = section_file.GetSize();
@ -683,7 +683,7 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
return Loader::ResultStatus::Error;
// We reopen the file, to allow its position to be independent from file's
FileUtil::IOFile romfs_file_inner(filepath, "rb");
Common::FS::IOFile romfs_file_inner(filepath, "rb");
if (!romfs_file_inner.IsOpen())
return Loader::ResultStatus::Error;
@ -698,10 +698,10 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
}
const auto path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
fmt::format("{}mods/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
GetModId(ncch_header.program_id));
if (use_layered_fs &&
(FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) {
(Common::FS::Exists(path + "romfs/") || Common::FS::Exists(path + "romfs_ext/"))) {
romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/",
path + "romfs_ext/");
@ -730,8 +730,8 @@ Loader::ResultStatus NCCHContainer::DumpRomFS(const std::string& target_path) {
Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReader>& romfs_file) {
// Check for RomFS overrides
std::string split_filepath = filepath + ".romfs";
if (FileUtil::Exists(split_filepath)) {
FileUtil::IOFile romfs_file_inner(split_filepath, "rb");
if (Common::FS::Exists(split_filepath)) {
Common::FS::IOFile romfs_file_inner(split_filepath, "rb");
if (romfs_file_inner.IsOpen()) {
LOG_WARNING(Service_FS, "File {} overriding built-in RomFS; LayeredFS not enabled",
split_filepath);

View File

@ -368,8 +368,8 @@ private:
u32 partition = 0;
std::string filepath;
FileUtil::IOFile file;
FileUtil::IOFile exefs_file;
Common::FS::IOFile file;
Common::FS::IOFile exefs_file;
};
} // namespace FileSys

View File

@ -4,7 +4,7 @@
#include <algorithm>
#include <set>
#include "common/file_util.h"
#include "common/fs/fs.h"
#include "common/string_util.h"
#include "core/file_sys/path_parser.h"
@ -59,7 +59,7 @@ PathParser::PathParser(const Path& path) {
PathParser::HostStatus PathParser::GetHostStatus(std::string_view mount_point) const {
std::string path{mount_point};
if (!FileUtil::IsDirectory(path))
if (!Common::FS::IsDir(path))
return InvalidMountPoint;
if (path_sequence.empty()) {
return DirectoryFound;
@ -70,17 +70,17 @@ PathParser::HostStatus PathParser::GetHostStatus(std::string_view mount_point) c
path += '/';
path += *iter;
if (!FileUtil::Exists(path))
if (!Common::FS::Exists(path))
return PathNotFound;
if (FileUtil::IsDirectory(path))
if (Common::FS::IsDir(path))
continue;
return FileInPath;
}
path += "/" + path_sequence.back();
if (!FileUtil::Exists(path))
if (!Common::FS::Exists(path))
return NotFound;
if (FileUtil::IsDirectory(path))
if (Common::FS::IsDir(path))
return DirectoryFound;
return FileFound;
}

View File

@ -5,6 +5,7 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include "core/file_sys/archive_backend.h"

View File

@ -30,11 +30,11 @@ private:
*/
class DirectRomFSReader : public RomFSReader {
public:
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
DirectRomFSReader(Common::FS::IOFile&& file, std::size_t file_offset, std::size_t data_size)
: is_encrypted(false), file(std::move(file)), file_offset(file_offset),
data_size(data_size) {}
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size,
DirectRomFSReader(Common::FS::IOFile&& file, std::size_t file_offset, std::size_t data_size,
const std::array<u8, 16>& key, const std::array<u8, 16>& ctr,
std::size_t crypto_offset)
: is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset),
@ -50,7 +50,7 @@ public:
private:
bool is_encrypted;
FileUtil::IOFile file;
Common::FS::IOFile file;
std::array<u8, 16> key;
std::array<u8, 16> ctr;
u64 file_offset;

View File

@ -79,14 +79,14 @@ ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& pa
return ERROR_FILE_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_FILE_NOT_FOUND;
@ -123,7 +123,7 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
if (Common::FS::Delete(full_path)) {
return RESULT_SUCCESS;
}
@ -150,7 +150,7 @@ ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_pa
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;
}
@ -200,12 +200,12 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
}
ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
return DeleteDirectoryHelper(path, mount_point, Common::FS::Delete);
}
ResultCode SaveDataArchive::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 SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
@ -237,11 +237,11 @@ ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) cons
}
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) {
@ -281,7 +281,7 @@ ResultCode SaveDataArchive::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;
}
@ -309,7 +309,7 @@ ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& de
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;
}

View File

@ -12,9 +12,9 @@ namespace FileSys {
bool SeedDB::Load() {
seeds.clear();
const std::string path{
fmt::format("{}/seeddb.bin", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
if (!FileUtil::Exists(path)) {
if (!FileUtil::CreateFullPath(path)) {
fmt::format("{}/seeddb.bin", Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir))};
if (!Common::FS::Exists(path)) {
if (!Common::FS::CreateFullPath(path)) {
LOG_ERROR(Service_FS, "Failed to create seed database");
return false;
}
@ -24,7 +24,7 @@ bool SeedDB::Load() {
}
return true;
}
FileUtil::IOFile file{path, "rb"};
Common::FS::IOFile file{path, "rb"};
if (!file.IsOpen()) {
LOG_ERROR(Service_FS, "Failed to open seed database");
return false;
@ -59,12 +59,12 @@ bool SeedDB::Load() {
bool SeedDB::Save() {
const std::string path{
fmt::format("{}/seeddb.bin", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
if (!FileUtil::CreateFullPath(path)) {
fmt::format("{}/seeddb.bin", Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir))};
if (!Common::FS::CreateFullPath(path)) {
LOG_ERROR(Service_FS, "Failed to create seed database");
return false;
}
FileUtil::IOFile file{path, "wb"};
Common::FS::IOFile file{path, "wb"};
if (!file.IsOpen()) {
LOG_ERROR(Service_FS, "Failed to open seed database");
return false;

View File

@ -17,7 +17,7 @@
namespace FileSys {
Loader::ResultStatus TitleMetadata::Load(const std::string& file_path) {
FileUtil::IOFile file(file_path, "rb");
Common::FS::IOFile file(file_path, "rb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;
@ -78,7 +78,7 @@ Loader::ResultStatus TitleMetadata::Load(const std::vector<u8>& file_data, std::
}
Loader::ResultStatus TitleMetadata::Save(const std::string& file_path) {
FileUtil::IOFile file(file_path, "wb");
Common::FS::IOFile file(file_path, "wb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;

View File

@ -18,7 +18,7 @@ void MiiSelector::Finalize(u32 return_code, HLE::Applets::MiiData mii) {
std::vector<HLE::Applets::MiiData> LoadMiis() {
std::vector<HLE::Applets::MiiData> miis;
std::string nand_directory{FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)};
std::string nand_directory{Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)};
FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true);
auto archive_result = extdata_archive_factory.Open(Service::PTM::ptm_shared_extdata_id, 0);

View File

@ -849,14 +849,14 @@ static void ReadMemory() {
SendReply("E01");
}
auto& memory = Core::System::GetInstance().Memory();
if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
addr)) {
if (!Memory::IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
addr)) {
return SendReply("E00");
}
std::vector<u8> data(len);
memory.ReadBlock(addr, data.data(), len);
Core::System::GetInstance().Memory().ReadBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len);
MemToGdbHex(reply, data.data(), len);
reply[len * 2] = '\0';
@ -873,16 +873,16 @@ static void WriteMemory() {
auto len_pos = std::find(start_offset, command_buffer + command_length, ':');
u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
auto& memory = Core::System::GetInstance().Memory();
if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
addr)) {
if (!Memory::IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
addr)) {
return SendReply("E00");
}
std::vector<u8> data(len);
GdbHexToMem(data.data(), len_pos + 1, len);
memory.WriteBlock(addr, data.data(), len);
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len);
Core::GetRunningCore().ClearInstructionCache();
SendReply("OK");
}

View File

@ -10,6 +10,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
@ -38,6 +39,7 @@
#include "core/hle/kernel/wait_object.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
namespace Kernel {
@ -372,7 +374,7 @@ ResultCode SVC::UnmapMemoryBlock(Handle handle, u32 addr) {
/// Connect to an OS service given the port name, returns the handle to the port to out
ResultCode SVC::ConnectToPort(Handle* out_handle, VAddr port_name_address) {
if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), port_name_address))
if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), port_name_address))
return ERR_NOT_FOUND;
static constexpr std::size_t PortNameMaxLength = 11;
@ -539,7 +541,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
bool wait_all, s64 nano_seconds) {
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
return ERR_INVALID_POINTER;
// NOTE: on real hardware, there is no nullptr check for 'out' (tested with firmware 4.4). If
@ -685,7 +687,7 @@ static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::Memory
/// In a single operation, sends a IPC reply and waits for a new request.
ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count,
Handle reply_target) {
if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
return ERR_INVALID_POINTER;
// Check if 'handle_count' is invalid

View File

@ -337,7 +337,8 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
}
// TODO(yuriks): Other checks, returning 0xD9001BEA
if (!memory.IsValidVirtualAddress(*owner_process, entry_point)) {
if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point);
// TODO: Verify error
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,

View File

@ -107,7 +107,7 @@ ResultCode CIAFile::WriteTitleMetadata() {
// If a TMD already exists for this app (ie 00000000.tmd), the incoming TMD
// will be the same plus one, (ie 00000001.tmd), both will be kept until
// the install is finalized and old contents can be discarded.
if (FileUtil::Exists(GetTitleMetadataPath(media_type, tmd.GetTitleID())))
if (Common::FS::Exists(GetTitleMetadataPath(media_type, tmd.GetTitleID())))
is_update = true;
std::string tmd_path = GetTitleMetadataPath(media_type, tmd.GetTitleID(), is_update);
@ -115,7 +115,7 @@ ResultCode CIAFile::WriteTitleMetadata() {
// Create content/ folder if it doesn't exist
std::string tmd_folder;
Common::SplitPath(tmd_path, &tmd_folder, nullptr, nullptr);
FileUtil::CreateFullPath(tmd_folder);
Common::FS::CreateFullPath(tmd_folder);
// Save TMD so that we can start getting new .app paths
if (tmd.Save(tmd_path) != Loader::ResultStatus::Success)
@ -126,7 +126,7 @@ ResultCode CIAFile::WriteTitleMetadata() {
Common::SplitPath(GetTitleContentPath(media_type, tmd.GetTitleID(),
FileSys::TMDContentIndex::Main, is_update),
&app_folder, nullptr, nullptr);
FileUtil::CreateFullPath(app_folder);
Common::FS::CreateFullPath(app_folder);
auto content_count = container.GetTitleMetadata().GetContentCount();
content_written.resize(content_count);
@ -169,7 +169,7 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
// Since the incoming TMD has already been written, we can use GetTitleContentPath
// to get the content paths to write to.
FileSys::TitleMetadata tmd = container.GetTitleMetadata();
FileUtil::IOFile file(GetTitleContentPath(media_type, tmd.GetTitleID(), i, is_update),
Common::FS::IOFile file(GetTitleContentPath(media_type, tmd.GetTitleID(), i, is_update),
content_written[i] ? "ab" : "wb");
if (!file.IsOpen()) {
@ -285,7 +285,7 @@ bool CIAFile::Close() const {
// Install aborted
if (!complete) {
LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install...");
FileUtil::DeleteDir(GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()));
Common::FS::Delete(GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()));
return true;
}
@ -294,7 +294,7 @@ bool CIAFile::Close() const {
GetTitleMetadataPath(media_type, container.GetTitleMetadata().GetTitleID(), false);
std::string new_tmd_path =
GetTitleMetadataPath(media_type, container.GetTitleMetadata().GetTitleID(), true);
if (FileUtil::Exists(new_tmd_path) && old_tmd_path != new_tmd_path) {
if (Common::FS::Exists(new_tmd_path) && old_tmd_path != new_tmd_path) {
FileSys::TitleMetadata old_tmd;
FileSys::TitleMetadata new_tmd;
@ -315,10 +315,10 @@ bool CIAFile::Close() const {
if (abort)
break;
FileUtil::Delete(GetTitleContentPath(media_type, old_tmd.GetTitleID(), old_index));
Common::FS::Delete(GetTitleContentPath(media_type, old_tmd.GetTitleID(), old_index));
}
FileUtil::Delete(old_tmd_path);
Common::FS::Delete(old_tmd_path);
}
return true;
}
@ -329,7 +329,7 @@ InstallStatus InstallCIA(const std::string& path,
std::function<ProgressCallback>&& update_callback) {
LOG_INFO(Service_AM, "Installing {}...", path);
if (!FileUtil::Exists(path)) {
if (!Common::FS::Exists(path)) {
LOG_ERROR(Service_AM, "File {} does not exist!", path);
return InstallStatus::ErrorFileNotFound;
}
@ -350,7 +350,7 @@ InstallStatus InstallCIA(const std::string& path,
}
}
FileUtil::IOFile file(path, "rb");
Common::FS::IOFile file(path, "rb");
if (!file.IsOpen())
return InstallStatus::ErrorFailedToOpenFile;
@ -374,11 +374,11 @@ InstallStatus InstallCIA(const std::string& path,
LOG_INFO(Service_AM, "Installed {} successfully.", path);
const FileUtil::DirectoryEntryCallable callback =
const Common::FS::DirectoryEntryCallable callback =
[&callback](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
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) {
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
if (!loader) {
@ -392,10 +392,10 @@ InstallStatus InstallCIA(const std::string& path,
}
return true;
} else {
return FileUtil::ForeachDirectoryEntry(nullptr, physical_name, callback);
return Common::FS::ForeachDirectoryEntry(nullptr, physical_name, callback);
}
};
if (!FileUtil::ForeachDirectoryEntry(
if (!Common::FS::ForeachDirectoryEntry(
nullptr,
GetTitlePath(
Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()),
@ -439,9 +439,9 @@ std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, boo
constexpr u32 MAX_TMD_ID = 0xFFFFFFFF;
u32 base_id = MAX_TMD_ID;
u32 update_id = 0;
FileUtil::FSTEntry entries;
FileUtil::ScanDirectoryTree(content_path, entries);
for (const FileUtil::FSTEntry& entry : entries.children) {
Common::FS::FSTEntry entries;
Common::FS::ScanDirectoryTree(content_path, entries);
for (const Common::FS::FSTEntry& entry : entries.children) {
std::string filename_filename, filename_extension;
Common::SplitPath(entry.virtualName, nullptr, &filename_filename, &filename_extension);
@ -522,12 +522,12 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) {
std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
if (media_type == Service::FS::MediaType::NAND)
return fmt::format("{}{}/title/", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
return fmt::format("{}{}/title/", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir),
SYSTEM_ID);
if (media_type == Service::FS::MediaType::SDMC)
return fmt::format("{}Nintendo 3DS/{}/{}/title/",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), SYSTEM_ID,
Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), SYSTEM_ID,
SDCARD_ID);
if (media_type == Service::FS::MediaType::GameCard) {
@ -546,10 +546,10 @@ void Module::ScanForTitles(Service::FS::MediaType media_type) {
std::string title_path = GetMediaTitlePath(media_type);
FileUtil::FSTEntry entries;
FileUtil::ScanDirectoryTree(title_path, entries, 1);
for (const FileUtil::FSTEntry& tid_high : entries.children) {
for (const FileUtil::FSTEntry& tid_low : tid_high.children) {
Common::FS::FSTEntry entries;
Common::FS::ScanDirectoryTree(title_path, entries, 1);
for (const Common::FS::FSTEntry& tid_high : entries.children) {
for (const Common::FS::FSTEntry& tid_low : tid_high.children) {
std::string tid_string = tid_high.virtualName + tid_low.virtualName;
if (tid_string.length() == TITLE_ID_VALID_LENGTH) {
@ -632,7 +632,7 @@ void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
content_info.ownership =
OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket.
if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, content_requested[i]))) {
if (Common::FS::Exists(GetTitleContentPath(media_type, title_id, content_requested[i]))) {
content_info.ownership |= OWNERSHIP_DOWNLOADED;
}
@ -684,7 +684,7 @@ void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) {
content_info.ownership =
OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket.
if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, i))) {
if (Common::FS::Exists(GetTitleContentPath(media_type, title_id, i))) {
content_info.ownership |= OWNERSHIP_DOWNLOADED;
}
@ -802,17 +802,17 @@ void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) {
}
LOG_INFO(Service_AM, "Deleting title 0x{:016x}", title_id);
std::string path = GetTitlePath(media_type, title_id);
if (!FileUtil::Exists(path)) {
if (!Common::FS::Exists(path)) {
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
ErrorLevel::Permanent));
LOG_ERROR(Service_AM, "Title not found");
return;
}
bool success = FileUtil::DeleteDirRecursively(path);
bool success = Common::FS::DeleteDirRecursively(path);
am->ScanForAllTitles();
rb.Push(RESULT_SUCCESS);
if (!success)
LOG_ERROR(Service_AM, "FileUtil::DeleteDirRecursively unexpectedly failed");
LOG_ERROR(Service_AM, "Common::FS::DeleteDirRecursively unexpectedly failed");
}
void Module::Interface::GetProductCode(Kernel::HLERequestContext& ctx) {
@ -821,7 +821,7 @@ void Module::Interface::GetProductCode(Kernel::HLERequestContext& ctx) {
u64 title_id = rp.Pop<u64>();
std::string path = GetTitleContentPath(media_type, title_id);
if (!FileUtil::Exists(path)) {
if (!Common::FS::Exists(path)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
ErrorLevel::Permanent));
@ -1016,7 +1016,7 @@ void Module::Interface::CheckContentRights(Kernel::HLERequestContext& ctx) {
// TODO(shinyquagsire23): Read tickets for this instead?
bool has_rights =
FileUtil::Exists(GetTitleContentPath(Service::FS::MediaType::SDMC, tid, content_index));
Common::FS::Exists(GetTitleContentPath(Service::FS::MediaType::SDMC, tid, content_index));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS); // No error
@ -1032,7 +1032,7 @@ void Module::Interface::CheckContentRightsIgnorePlatform(Kernel::HLERequestConte
// TODO(shinyquagsire23): Read tickets for this instead?
bool has_rights =
FileUtil::Exists(GetTitleContentPath(Service::FS::MediaType::SDMC, tid, content_index));
Common::FS::Exists(GetTitleContentPath(Service::FS::MediaType::SDMC, tid, content_index));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS); // No error
@ -1396,17 +1396,17 @@ void Module::Interface::DeleteProgram(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_AM, "Deleting title 0x{:016x}", title_id);
std::string path = GetTitlePath(media_type, title_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (!FileUtil::Exists(path)) {
if (!Common::FS::Exists(path)) {
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
ErrorLevel::Permanent));
LOG_ERROR(Service_AM, "Title not found");
return;
}
bool success = FileUtil::DeleteDirRecursively(path);
bool success = Common::FS::DeleteDirRecursively(path);
am->ScanForAllTitles();
rb.Push(RESULT_SUCCESS);
if (!success)
LOG_ERROR(Service_AM, "FileUtil::DeleteDirRecursively unexpectedly failed");
LOG_ERROR(Service_AM, "Common::FS::DeleteDirRecursively unexpectedly failed");
}
void Module::Interface::GetSystemUpdaterMutex(Kernel::HLERequestContext& ctx) {

View File

@ -213,10 +213,10 @@ bool Module::LoadLegacySharedFont() {
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
// "shared_font.bin" in the Citra "sysdata" directory.
std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SHARED_FONT;
std::string filepath = Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir) + SHARED_FONT;
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
Common::FS::CreateFullPath(filepath); // Create path if not already created
Common::FS::IOFile file(filepath, "rb");
if (file.IsOpen()) {
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
return true;

View File

@ -1380,7 +1380,7 @@ Module::Module(Core::System& system) : system(system) {
change_state_event =
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "CECD::change_state_event");
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
// Open the SystemSaveData archive 0x00010026

View File

@ -560,7 +560,7 @@ ResultCode Module::FormatConfig() {
} // namespace Service::CFG
ResultCode Module::LoadConfigNANDSaveFile() {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
// Open the SystemSaveData archive 0x00010017

View File

@ -32,10 +32,10 @@
namespace Service::FS {
MediaType GetMediaTypeFromPath(std::string_view path) {
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 0) == 0) {
if (path.rfind(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir), 0) == 0) {
return MediaType::NAND;
}
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 0) == 0) {
if (path.rfind(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), 0) == 0) {
return MediaType::SDMC;
}
return MediaType::GameCard;
@ -263,9 +263,9 @@ ResultCode ArchiveManager::DeleteExtSaveData(MediaType media_type, u32 high, u32
std::string media_type_directory;
if (media_type == MediaType::NAND) {
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
media_type_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
} else if (media_type == MediaType::SDMC) {
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
media_type_directory = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
} else {
LOG_ERROR(Service_FS, "Unsupported media type {}", media_type);
return ResultCode(-1); // TODO(Subv): Find the right error code
@ -275,7 +275,7 @@ ResultCode ArchiveManager::DeleteExtSaveData(MediaType media_type, u32 high, u32
std::string base_path =
FileSys::GetExtDataContainerPath(media_type_directory, media_type == MediaType::NAND);
std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path);
if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path))
if (Common::FS::Exists(extsavedata_path) && !Common::FS::DeleteDirRecursively(extsavedata_path))
return ResultCode(-1); // TODO(Subv): Find the right error code
return RESULT_SUCCESS;
}
@ -284,10 +284,10 @@ ResultCode ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) {
// Construct the binary path to the archive first
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low);
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory);
const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path);
if (!FileUtil::DeleteDirRecursively(systemsavedata_path)) {
if (!Common::FS::DeleteDirRecursively(systemsavedata_path)) {
return ResultCode(-1); // TODO(Subv): Find the right error code
}
@ -298,10 +298,10 @@ ResultCode ArchiveManager::CreateSystemSaveData(u32 high, u32 low) {
// Construct the binary path to the archive first
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low);
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory);
const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path);
if (!FileUtil::CreateFullPath(systemsavedata_path)) {
if (!Common::FS::CreateFullPath(systemsavedata_path)) {
return ResultCode(-1); // TODO(Subv): Find the right error code
}
@ -322,8 +322,8 @@ void ArchiveManager::RegisterArchiveTypes() {
// TODO(Subv): Add the other archive types (see here for the known types:
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
std::string sdmc_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
std::string sdmc_directory = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
std::string nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
auto sdmc_factory = std::make_unique<FileSys::ArchiveFactory_SDMC>(sdmc_directory);
if (sdmc_factory->Initialize())
RegisterArchiveType(std::move(sdmc_factory), ArchiveIdCode::SDMC);

View File

@ -492,7 +492,10 @@ void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) {
auto node_it = std::find_if(node_info.begin(), node_info.end(), [&node](const NodeInfo& info) {
return info.network_node_id == node.node_id;
});
ASSERT(node_it != node_info.end());
if (node_it == node_info.end()) {
LOG_ERROR(Service_NWM, "node_it is last node of node_info");
return;
}
connection_status.node_bitmask &= ~(1 << (node.node_id - 1));
connection_status.changed_nodes |= 1 << (node.node_id - 1);
@ -1097,9 +1100,6 @@ void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) {
u32 data_size = rp.Pop<u32>();
u8 flags = rp.Pop<u8>();
// There should never be a dest_node_id of 0
ASSERT(dest_node_id != 0);
std::vector<u8> input_buffer = rp.PopStaticBuffer();
ASSERT(input_buffer.size() >= data_size);
input_buffer.resize(data_size);
@ -1114,6 +1114,14 @@ void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) {
return;
}
// There should never be a dest_node_id of 0
if (dest_node_id == 0) {
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
ErrorSummary::WrongArgument, ErrorLevel::Status));
LOG_ERROR(Service_NWM, "dest_node_id is 0");
return;
}
if (dest_node_id == connection_status.network_node_id) {
LOG_ERROR(Service_NWM, "tried to send packet to itself");
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,

View File

@ -134,7 +134,7 @@ void Module::Interface::CheckNew3DS(Kernel::HLERequestContext& ctx) {
}
static void WriteGameCoinData(GameCoin gamecoin_data) {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true);
FileSys::Path archive_path(ptm_shared_extdata_id);
@ -167,7 +167,7 @@ static void WriteGameCoinData(GameCoin gamecoin_data) {
}
static GameCoin ReadGameCoinData() {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true);
FileSys::Path archive_path(ptm_shared_extdata_id);
@ -197,7 +197,7 @@ static GameCoin ReadGameCoinData() {
Module::Module() {
// Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't
// exist
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string& nand_directory = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true);
const FileSys::Path archive_path(ptm_shared_extdata_id);
const auto archive_result = extdata_archive_factory.Open(archive_path, 0);

View File

@ -212,19 +212,25 @@ struct CTRPollFD {
/// Translates the resulting events of a Poll operation from 3ds specific to platform
/// specific
static u32 TranslateToPlatform(Events input_event) {
static u32 TranslateToPlatform(Events input_event, bool isOutput) {
#if _WIN32
constexpr bool isWin = true;
#else
constexpr bool isWin = false;
#endif
u32 ret = 0;
if (input_event.pollin)
ret |= POLLIN;
if (input_event.pollpri)
if (input_event.pollpri && !isWin)
ret |= POLLPRI;
if (input_event.pollhup)
if (input_event.pollhup && (!isWin || isOutput))
ret |= POLLHUP;
if (input_event.pollerr)
if (input_event.pollerr && (!isWin || isOutput))
ret |= POLLERR;
if (input_event.pollout)
ret |= POLLOUT;
if (input_event.pollnval)
if (input_event.pollnval && (isWin && isOutput))
ret |= POLLNVAL;
return ret;
}
@ -233,20 +239,26 @@ struct CTRPollFD {
Events revents; ///< Events received (output)
/// Converts a platform-specific pollfd to a 3ds specific structure
static CTRPollFD FromPlatform(pollfd const& fd) {
static CTRPollFD FromPlatform(SOC::SOC_U& socu, pollfd const& fd) {
CTRPollFD result;
result.events.hex = Events::TranslateTo3DS(fd.events).hex;
result.revents.hex = Events::TranslateTo3DS(fd.revents).hex;
result.fd = static_cast<u32>(fd.fd);
for (auto iter = socu.open_sockets.begin(); iter != socu.open_sockets.end(); ++iter) {
if (iter->second.socket_fd == fd.fd) {
result.fd = iter->first;
break;
}
}
return result;
}
/// Converts a 3ds specific pollfd to a platform-specific structure
static pollfd ToPlatform(CTRPollFD const& fd) {
static pollfd ToPlatform(SOC::SOC_U& socu, CTRPollFD const& fd) {
pollfd result;
result.events = Events::TranslateToPlatform(fd.events);
result.revents = Events::TranslateToPlatform(fd.revents);
result.fd = fd.fd;
result.events = Events::TranslateToPlatform(fd.events, false);
result.revents = Events::TranslateToPlatform(fd.revents, true);
auto iter = socu.open_sockets.find(fd.fd);
result.fd = (iter != socu.open_sockets.end()) ? iter->second.socket_fd : 0;
return result;
}
};
@ -342,6 +354,14 @@ struct CTRAddrInfo {
static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct");
void SOC_U::PreTimerAdjust() {
timer_adjust_handle = Core::System::GetInstance().GetRunningCore().GetTimer().StartAdjust();
}
void SOC_U::PostTimerAdjust() {
Core::System::GetInstance().GetRunningCore().GetTimer().EndAdjust(timer_adjust_handle);
}
void SOC_U::CleanupSockets() {
for (auto sock : open_sockets)
closesocket(sock.second.socket_fd);
@ -376,21 +396,28 @@ void SOC_U::Socket(Kernel::HLERequestContext& ctx) {
return;
}
u32 ret = static_cast<u32>(::socket(domain, type, protocol));
u64 ret = static_cast<u64>(::socket(domain, type, protocol));
u32 socketHandle = GetNextSocketID();
if ((s32)ret != SOCKET_ERROR_VALUE)
open_sockets[ret] = {ret, true};
if ((s64)ret != SOCKET_ERROR_VALUE)
open_sockets[socketHandle] = {static_cast<decltype(SocketHolder::socket_fd)>(ret), true};
if ((s32)ret == SOCKET_ERROR_VALUE)
if ((s64)ret == SOCKET_ERROR_VALUE)
ret = TranslateError(GET_ERRNO);
rb.Push(RESULT_SUCCESS);
rb.Push(ret);
rb.Push(socketHandle);
}
void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x05, 2, 4);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 len = rp.Pop<u32>();
rp.PopPID();
auto sock_addr_buf = rp.PopStaticBuffer();
@ -400,7 +427,7 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr);
s32 ret = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len));
s32 ret = ::bind(fd_info->second.socket_fd, &sock_addr, std::max<u32>(sizeof(sock_addr), len));
if (ret != 0)
ret = TranslateError(GET_ERRNO);
@ -413,6 +440,12 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x13, 3, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 ctr_cmd = rp.Pop<u32>();
u32 ctr_arg = rp.Pop<u32>();
rp.PopPID();
@ -427,11 +460,10 @@ void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
if (ctr_cmd == 3) { // F_GETFL
#ifdef _WIN32
posix_ret = 0;
auto iter = open_sockets.find(socket_handle);
if (iter != open_sockets.end() && iter->second.blocking == false)
if (fd_info->second.blocking == false)
posix_ret |= 4; // O_NONBLOCK
#else
int ret = ::fcntl(socket_handle, F_GETFL, 0);
int ret = ::fcntl(fd_info->second.socket_fd, F_GETFL, 0);
if (ret == SOCKET_ERROR_VALUE) {
posix_ret = TranslateError(GET_ERRNO);
return;
@ -443,7 +475,7 @@ void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
} else if (ctr_cmd == 4) { // F_SETFL
#ifdef _WIN32
unsigned long tmp = (ctr_arg & 4 /* O_NONBLOCK */) ? 1 : 0;
int ret = ioctlsocket(socket_handle, FIONBIO, &tmp);
int ret = ioctlsocket(fd_info->second.socket_fd, FIONBIO, &tmp);
if (ret == SOCKET_ERROR_VALUE) {
posix_ret = TranslateError(GET_ERRNO);
return;
@ -452,7 +484,7 @@ void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
if (iter != open_sockets.end())
iter->second.blocking = (tmp == 0);
#else
int flags = ::fcntl(socket_handle, F_GETFL, 0);
int flags = ::fcntl(fd_info->second.socket_fd, F_GETFL, 0);
if (flags == SOCKET_ERROR_VALUE) {
posix_ret = TranslateError(GET_ERRNO);
return;
@ -462,7 +494,7 @@ void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
if (ctr_arg & 4) // O_NONBLOCK
flags |= O_NONBLOCK;
int ret = ::fcntl(socket_handle, F_SETFL, flags);
int ret = ::fcntl(fd_info->second.socket_fd, F_SETFL, flags);
if (ret == SOCKET_ERROR_VALUE) {
posix_ret = TranslateError(GET_ERRNO);
return;
@ -478,10 +510,16 @@ void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
void SOC_U::Listen(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x03, 2, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 backlog = rp.Pop<u32>();
rp.PopPID();
s32 ret = ::listen(socket_handle, backlog);
s32 ret = ::listen(fd_info->second.socket_fd, backlog);
if (ret != 0)
ret = TranslateError(GET_ERRNO);
@ -496,11 +534,19 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
// performing nonblocking operations and spinlock until the data is available
IPC::RequestParser rp(ctx, 0x04, 2, 2);
const auto socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
[[maybe_unused]] const auto max_addr_len = static_cast<socklen_t>(rp.Pop<u32>());
rp.PopPID();
sockaddr addr;
socklen_t addr_len = sizeof(addr);
u32 ret = static_cast<u32>(::accept(socket_handle, &addr, &addr_len));
PreTimerAdjust();
u32 ret = static_cast<u32>(::accept(fd_info->second.socket_fd, &addr, &addr_len));
PostTimerAdjust();
if (static_cast<s32>(ret) != SOCKET_ERROR_VALUE) {
open_sockets[ret] = {ret, true};
@ -543,12 +589,21 @@ void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) {
void SOC_U::Close(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0B, 1, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
rp.PopPID();
s32 ret = 0;
open_sockets.erase(socket_handle);
ret = closesocket(socket_handle);
PreTimerAdjust();
ret = closesocket(fd_info->second.socket_fd);
PostTimerAdjust();
open_sockets.erase(socket_handle);
if (ret != 0)
ret = TranslateError(GET_ERRNO);
@ -561,6 +616,12 @@ void SOC_U::Close(Kernel::HLERequestContext& ctx) {
void SOC_U::SendTo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0A, 4, 6);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 len = rp.Pop<u32>();
u32 flags = rp.Pop<u32>();
u32 addr_len = rp.Pop<u32>();
@ -569,16 +630,18 @@ void SOC_U::SendTo(Kernel::HLERequestContext& ctx) {
auto dest_addr_buff = rp.PopStaticBuffer();
s32 ret = -1;
PreTimerAdjust();
if (addr_len > 0) {
CTRSockAddr ctr_dest_addr;
std::memcpy(&ctr_dest_addr, dest_addr_buff.data(), sizeof(ctr_dest_addr));
sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags,
&dest_addr, sizeof(dest_addr));
ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast<const char*>(input_buff.data()),
len, flags, &dest_addr, sizeof(dest_addr));
} else {
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags,
nullptr, 0);
ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast<const char*>(input_buff.data()),
len, flags, nullptr, 0);
}
PostTimerAdjust();
if (ret == SOCKET_ERROR_VALUE)
ret = TranslateError(GET_ERRNO);
@ -591,6 +654,12 @@ void SOC_U::SendTo(Kernel::HLERequestContext& ctx) {
void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x7, 4, 4);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 len = rp.Pop<u32>();
u32 flags = rp.Pop<u32>();
u32 addr_len = rp.Pop<u32>();
@ -604,19 +673,20 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
socklen_t src_addr_len = sizeof(src_addr);
s32 ret = -1;
PreTimerAdjust();
if (addr_len > 0) {
ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags,
&src_addr, &src_addr_len);
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
len, flags, &src_addr, &src_addr_len);
if (ret >= 0 && src_addr_len > 0) {
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
std::memcpy(addr_buff.data(), &ctr_src_addr, sizeof(ctr_src_addr));
}
} else {
ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags,
NULL, 0);
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
len, flags, NULL, 0);
addr_buff.resize(0);
}
PostTimerAdjust();
if (ret == SOCKET_ERROR_VALUE) {
ret = TranslateError(GET_ERRNO);
} else {
@ -636,6 +706,12 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
// performing nonblocking operations and spinlock until the data is available
IPC::RequestParser rp(ctx, 0x08, 4, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 len = rp.Pop<u32>();
u32 flags = rp.Pop<u32>();
u32 addr_len = rp.Pop<u32>();
@ -648,19 +724,21 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
socklen_t src_addr_len = sizeof(src_addr);
s32 ret = -1;
PreTimerAdjust();
if (addr_len > 0) {
// Only get src adr if input adr available
ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags,
&src_addr, &src_addr_len);
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
len, flags, &src_addr, &src_addr_len);
if (ret >= 0 && src_addr_len > 0) {
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
std::memcpy(addr_buff.data(), &ctr_src_addr, sizeof(ctr_src_addr));
}
} else {
ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags,
NULL, 0);
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
len, flags, NULL, 0);
addr_buff.resize(0);
}
PostTimerAdjust();
s32 total_received = ret;
if (ret == SOCKET_ERROR_VALUE) {
@ -691,21 +769,32 @@ void SOC_U::Poll(Kernel::HLERequestContext& ctx) {
// The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different
// sizes)
// so we have to copy the data
// so we have to copy the data in order
std::vector<pollfd> platform_pollfd(nfds);
std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform);
for (u32 i = 0; i < nfds; i++) {
platform_pollfd[i] = CTRPollFD::ToPlatform(*this, ctr_fds[i]);
}
PreTimerAdjust();
s32 ret = ::poll(platform_pollfd.data(), nfds, timeout);
PostTimerAdjust();
// Now update the output pollfd structure
std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(),
CTRPollFD::FromPlatform);
// Now update the output 3ds_pollfd structure
for (u32 i = 0; i < nfds; i++) {
ctr_fds[i] = CTRPollFD::FromPlatform(*this, platform_pollfd[i]);
}
std::vector<u8> output_fds(nfds * sizeof(CTRPollFD));
std::memcpy(output_fds.data(), ctr_fds.data(), nfds * sizeof(CTRPollFD));
if (ret == SOCKET_ERROR_VALUE)
if (ret == SOCKET_ERROR_VALUE) {
int err = GET_ERRNO;
LOG_ERROR(Service_SOC, "Socket error: {}", err);
ret = TranslateError(GET_ERRNO);
}
size_t test = platform_pollfd.size();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS);
@ -716,12 +805,18 @@ void SOC_U::Poll(Kernel::HLERequestContext& ctx) {
void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x17, 2, 2);
const auto socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
[[maybe_unused]] const auto max_addr_len = rp.Pop<u32>();
rp.PopPID();
sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr);
s32 ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len);
s32 ret = ::getsockname(fd_info->second.socket_fd, &dest_addr, &dest_addr_len);
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
@ -739,10 +834,16 @@ void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
void SOC_U::Shutdown(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0C, 2, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
s32 how = rp.Pop<s32>();
rp.PopPID();
s32 ret = ::shutdown(socket_handle, how);
s32 ret = ::shutdown(fd_info->second.socket_fd, how);
if (ret != 0)
ret = TranslateError(GET_ERRNO);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
@ -753,12 +854,18 @@ void SOC_U::Shutdown(Kernel::HLERequestContext& ctx) {
void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x18, 2, 2);
const auto socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
[[maybe_unused]] const auto max_addr_len = rp.Pop<u32>();
rp.PopPID();
sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr);
const int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len);
const int ret = ::getpeername(fd_info->second.socket_fd, &dest_addr, &dest_addr_len);
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
@ -781,6 +888,12 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
// performing nonblocking operations and spinlock until the data is available
IPC::RequestParser rp(ctx, 0x06, 2, 4);
const auto socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
[[maybe_unused]] const auto input_addr_len = rp.Pop<u32>();
rp.PopPID();
auto input_addr_buf = rp.PopStaticBuffer();
@ -789,7 +902,9 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
std::memcpy(&ctr_input_addr, input_addr_buf.data(), sizeof(ctr_input_addr));
sockaddr input_addr = CTRSockAddr::ToPlatform(ctr_input_addr);
s32 ret = ::connect(socket_handle, &input_addr, sizeof(input_addr));
PreTimerAdjust();
s32 ret = ::connect(fd_info->second.socket_fd, &input_addr, sizeof(input_addr));
PostTimerAdjust();
if (ret != 0)
ret = TranslateError(GET_ERRNO);
@ -821,6 +936,12 @@ void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) {
void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x11, 4, 2);
u32 socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
u32 level = rp.Pop<u32>();
s32 optname = rp.Pop<s32>();
socklen_t optlen = static_cast<socklen_t>(rp.Pop<u32>());
@ -838,7 +959,7 @@ void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
#endif
} else {
char* optval_data = reinterpret_cast<char*>(optval.data());
err = ::getsockopt(socket_handle, level, optname, optval_data, &optlen);
err = ::getsockopt(fd_info->second.socket_fd, level, optname, optval_data, &optlen);
if (err == SOCKET_ERROR_VALUE) {
err = TranslateError(GET_ERRNO);
}
@ -854,6 +975,12 @@ void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
void SOC_U::SetSockOpt(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x12, 4, 4);
const auto socket_handle = rp.Pop<u32>();
auto fd_info = open_sockets.find(socket_handle);
if (fd_info == open_sockets.end()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ERR_INVALID_HANDLE);
return;
}
const auto level = rp.Pop<u32>();
const auto optname = rp.Pop<s32>();
[[maybe_unused]] const auto optlen = static_cast<socklen_t>(rp.Pop<u32>());
@ -870,7 +997,7 @@ void SOC_U::SetSockOpt(Kernel::HLERequestContext& ctx) {
#endif
} else {
const char* optval_data = reinterpret_cast<const char*>(optval.data());
err = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval_data,
err = static_cast<u32>(::setsockopt(fd_info->second.socket_fd, level, optname, optval_data,
static_cast<socklen_t>(optval.size())));
if (err == SOCKET_ERROR_VALUE) {
err = TranslateError(GET_ERRNO);

View File

@ -6,6 +6,7 @@
#include <unordered_map>
#include <boost/serialization/unordered_map.hpp>
#include "core/hle/result.h"
#include "core/hle/service/service.h"
namespace Core {
@ -16,7 +17,13 @@ namespace Service::SOC {
/// Holds information about a particular socket
struct SocketHolder {
#ifdef _WIN32
using SOCKET = unsigned long long;
SOCKET socket_fd; ///< The socket descriptor
#else
u32 socket_fd; ///< The socket descriptor
#endif // _WIN32
bool blocking; ///< Whether the socket is blocking or not, it is only read on Windows.
private:
@ -34,6 +41,10 @@ public:
~SOC_U();
private:
static constexpr ResultCode ERR_INVALID_HANDLE =
ResultCode(ErrorDescription::InvalidHandle, ErrorModule::SOC, ErrorSummary::InvalidArgument,
ErrorLevel::Permanent);
void Socket(Kernel::HLERequestContext& ctx);
void Bind(Kernel::HLERequestContext& ctx);
void Fcntl(Kernel::HLERequestContext& ctx);
@ -59,16 +70,29 @@ private:
void GetAddrInfoImpl(Kernel::HLERequestContext& ctx);
void GetNameInfoImpl(Kernel::HLERequestContext& ctx);
// Socked ids
u32 next_socket_id = 3;
u32 GetNextSocketID() {
return next_socket_id++;
}
// System timer adjust
u32 timer_adjust_handle;
void PreTimerAdjust();
void PostTimerAdjust();
/// Close all open sockets
void CleanupSockets();
/// Holds info about the currently open sockets
friend struct CTRPollFD;
std::unordered_map<u32, SocketHolder> open_sockets;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar& open_sockets;
ar& timer_adjust_handle;
}
friend class boost::serialization::access;
};

View File

@ -159,8 +159,8 @@ void LoadBootromKeys() {
// by other applications e.g. process9. These normal keys thus aren't used by any application
// and have no value for emulation
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
auto file = FileUtil::IOFile(filepath, "rb");
const std::string filepath = Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir) + BOOTROM9;
auto file = Common::FS::IOFile(filepath, "rb");
if (!file) {
return;
}
@ -310,8 +310,8 @@ void LoadNativeFirmKeysNew3DS() {
// The first 0x10 bytes of the secret_sector are used as a key to decrypt a KeyX from the
// native_firm
const std::string filepath =
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SECRET_SECTOR;
auto secret = FileUtil::IOFile(filepath, "rb");
Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir) + SECRET_SECTOR;
auto secret = Common::FS::IOFile(filepath, "rb");
if (!secret) {
return;
}
@ -426,8 +426,8 @@ void LoadNativeFirmKeysNew3DS() {
}
void LoadPresetKeys() {
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS;
FileUtil::CreateFullPath(filepath); // Create path if not already created
const std::string filepath = Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir) + AES_KEYS;
Common::FS::CreateFullPath(filepath); // Create path if not already created
std::ifstream file;
OpenFStream(file, filepath, std::ios_base::in);
if (!file) {

View File

@ -52,8 +52,8 @@ void InitSlots() {
return;
initialized = true;
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
FileUtil::IOFile file(filepath, "rb");
const std::string filepath = Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir) + BOOTROM9;
Common::FS::IOFile file(filepath, "rb");
if (!file) {
return;
}

View File

@ -94,7 +94,7 @@ static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets)
using Kernel::CodeSet;
static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr,
static THREEDSX_Error Load3DSXFile(Common::FS::IOFile& file, u32 base_addr,
std::shared_ptr<CodeSet>* out_codeset) {
if (!file.IsOpen())
return ERROR_FILE;
@ -248,7 +248,7 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr,
return ERROR_NONE;
}
FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) {
FileType AppLoader_THREEDSX::IdentifyType(Common::FS::IOFile& file) {
u32 magic;
file.Seek(0, SEEK_SET);
if (1 != file.ReadArray<u32>(&magic, 1))
@ -315,7 +315,7 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>
LOG_DEBUG(Loader, "RomFS size: {:#010X}", romfs_size);
// We reopen the file, to allow its position to be independent from file's
FileUtil::IOFile romfs_file_inner(filepath, "rb");
Common::FS::IOFile romfs_file_inner(filepath, "rb");
if (!romfs_file_inner.IsOpen())
return ResultStatus::Error;

View File

@ -17,16 +17,16 @@ namespace Loader {
/// Loads an 3DSX file
class AppLoader_THREEDSX final : public AppLoader {
public:
AppLoader_THREEDSX(FileUtil::IOFile&& file, const std::string& filename,
AppLoader_THREEDSX(Common::FS::IOFile&& file, const std::string& filename,
const std::string& filepath)
: AppLoader(std::move(file)), filename(std::move(filename)), filepath(filepath) {}
/**
* Returns the type of the file
* @param file FileUtil::IOFile open file
* @param file Common::FS::IOFile open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(FileUtil::IOFile& file);
static FileType IdentifyType(Common::FS::IOFile& file);
FileType GetFileType() override {
return IdentifyType(file);

View File

@ -364,7 +364,7 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
namespace Loader {
FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file) {
FileType AppLoader_ELF::IdentifyType(Common::FS::IOFile& file) {
u32 magic;
file.Seek(0, SEEK_SET);
if (1 != file.ReadArray<u32>(&magic, 1))

View File

@ -17,15 +17,15 @@ namespace Loader {
/// Loads an ELF/AXF file
class AppLoader_ELF final : public AppLoader {
public:
AppLoader_ELF(FileUtil::IOFile&& file, std::string filename)
AppLoader_ELF(Common::FS::IOFile&& file, std::string filename)
: AppLoader(std::move(file)), filename(std::move(filename)) {}
/**
* Returns the type of the file
* @param file FileUtil::IOFile open file
* @param file Common::FS::IOFile open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(FileUtil::IOFile& file);
static FileType IdentifyType(Common::FS::IOFile& file);
FileType GetFileType() override {
return IdentifyType(file);

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