Compare commits
140 Commits
auto-objec
...
fs-fixes
Author | SHA1 | Date | |
---|---|---|---|
c02b0a0113 | |||
97cbcc6e49 | |||
9d6aa2db81 | |||
0675bd946c | |||
281f2926bb | |||
349ac6ac05 | |||
16571a96a2 | |||
a0e9836386 | |||
fb10d05f97 | |||
182bebf272 | |||
7626804a90 | |||
20496e8ca4 | |||
b4184a3c2b | |||
2251d96d4d | |||
c028a8c7c5 | |||
359f97be22 | |||
748f8a0658 | |||
a7611bb2d3 | |||
6f35a3bf37 | |||
58573dd7b3 | |||
11728d6772 | |||
a99be221b2 | |||
f656610a41 | |||
cfa1a7b91c | |||
52b1fc4889 | |||
01e2b6cdaa | |||
f75380fc93 | |||
18af49a0ca | |||
c7e64f6c7b | |||
0a40a513a6 | |||
a4dc6a55b7 | |||
7dce5be263 | |||
3f7d97da4c | |||
a11b4dd051 | |||
c78847b2b6 | |||
8faa7a6e02 | |||
acf4b4e5fb | |||
3a0ca63d91 | |||
1e96775203 | |||
6d27e8be8d | |||
c357a8b9b6 | |||
7ad982f123 | |||
dd3d24bfec | |||
489bbb98b2 | |||
5c9543e39d | |||
49085d400c | |||
8be9ea4f4a | |||
a71c288252 | |||
159809eb32 | |||
2a4f0ce8de | |||
4c8f1c83c8 | |||
98f6d697d8 | |||
4636735783 | |||
8a2770bf83 | |||
f1e09c1ea1 | |||
2423e645f1 | |||
0eaae31f9f | |||
4f9b545296 | |||
628d70e112 | |||
9991b9b12b | |||
e4bcf73c5a | |||
b693d205e4 | |||
91621ec202 | |||
948f72d320 | |||
8c5b417486 | |||
51685ee2db | |||
079e4aa205 | |||
48edfb891b | |||
915406354c | |||
6f3fc32a93 | |||
ebfa98d31d | |||
3095ee91a8 | |||
fadeecfe6d | |||
2a1598036e | |||
58f01112c5 | |||
7e7b3dc18c | |||
d4a3f60575 | |||
f8cbf783cb | |||
dc0cddb7de | |||
9cef9d4c58 | |||
dada05801f | |||
9bc71a3307 | |||
98274273b1 | |||
269db2bfb8 | |||
d27c1c8606 | |||
891b4bff18 | |||
eeccdc02fc | |||
29ee94c3f5 | |||
8936641841 | |||
e1f6b88e7b | |||
64d809f06a | |||
ebd23026a0 | |||
6e1bfe9949 | |||
854092ce4f | |||
90d24caaf8 | |||
65400936c7 | |||
3f9e5a2b42 | |||
34ba320c3d | |||
ec9f1902f5 | |||
c72a365d78 | |||
8f211613a3 | |||
30885b72be | |||
beb078a71b | |||
a65f9ea5a8 | |||
f9c11eab96 | |||
794f6e4a67 | |||
c85731f3ae | |||
e1542cea84 | |||
887ef51f04 | |||
d809687aeb | |||
96ec85a72e | |||
0ed4d493ad | |||
78be1e7c17 | |||
2963682722 | |||
98eea4dcca | |||
98a4a18201 | |||
3619bd33b1 | |||
fa870be263 | |||
3a6d19f51f | |||
73d6a9d585 | |||
5d48107dd6 | |||
a306931e1c | |||
b3803c5002 | |||
c412c116d8 | |||
6ce4493e14 | |||
d6e545932a | |||
542bae4581 | |||
307154a06f | |||
725afe33ef | |||
bb58056ebe | |||
f69a33574c | |||
1a48cf7e7d | |||
2833d94a3b | |||
9b0aa5135e | |||
9787efc7ee | |||
623293d272 | |||
5ab5fdcc22 | |||
1b1988a37a | |||
f584d143ff | |||
56c679595f |
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -58,3 +58,12 @@
|
|||||||
[submodule "sdl2"]
|
[submodule "sdl2"]
|
||||||
path = externals/sdl2/SDL
|
path = externals/sdl2/SDL
|
||||||
url = https://github.com/libsdl-org/SDL
|
url = https://github.com/libsdl-org/SDL
|
||||||
|
[submodule "vulkan-headers"]
|
||||||
|
path = externals/vulkan-headers
|
||||||
|
url = https://github.com/KhronosGroup/Vulkan-Headers
|
||||||
|
[submodule "glslang"]
|
||||||
|
path = externals/glslang
|
||||||
|
url = https://github.com/KhronosGroup/glslang
|
||||||
|
[submodule "glm"]
|
||||||
|
path = externals/glm
|
||||||
|
url = https://github.com/g-truc/glm
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# CMake 3.12 required for 20 to be a valid value for CXX_STANDARD
|
# CMake 3.12 required for 20 to be a valid value for CXX_STANDARD
|
||||||
cmake_minimum_required(VERSION 3.12)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15)
|
|
||||||
# Don't override the warning flags in MSVC:
|
# Don't override the warning flags in MSVC:
|
||||||
cmake_policy(SET CMP0092 NEW)
|
cmake_policy(SET CMP0092 NEW)
|
||||||
# Enforce new LTO setting
|
# Enforce new LTO setting
|
||||||
cmake_policy(SET CMP0069 NEW)
|
cmake_policy(SET CMP0069 NEW)
|
||||||
endif()
|
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||||
include(DownloadExternals)
|
include(DownloadExternals)
|
||||||
|
include(GNUInstallDirs)
|
||||||
include(CMakeDependentOption)
|
include(CMakeDependentOption)
|
||||||
|
|
||||||
project(citra LANGUAGES C CXX ASM)
|
project(citra LANGUAGES C CXX ASM)
|
||||||
|
2
dist/qt_themes/colorful/style.qrc
vendored
2
dist/qt_themes/colorful/style.qrc
vendored
@ -13,6 +13,6 @@
|
|||||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="colorful">
|
<qresource prefix="colorful">
|
||||||
<file>style.qss</file>
|
<file alias="style.qss">../default/style.qss</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
4
dist/qt_themes/colorful/style.qss
vendored
4
dist/qt_themes/colorful/style.qss
vendored
@ -1,4 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is intentionally left blank.
|
|
||||||
We do not want to apply any stylesheet for colorful, only icons.
|
|
||||||
*/
|
|
17
dist/qt_themes/default/default.qrc
vendored
17
dist/qt_themes/default/default.qrc
vendored
@ -1,33 +1,22 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="icons/default">
|
<qresource prefix="icons/default">
|
||||||
<file alias="index.theme">icons/index.theme</file>
|
<file alias="index.theme">icons/index.theme</file>
|
||||||
|
|
||||||
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
||||||
|
|
||||||
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
||||||
|
|
||||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||||
|
|
||||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||||
|
|
||||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||||
|
|
||||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||||
|
|
||||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||||
|
|
||||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||||
|
|
||||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||||
|
|
||||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||||
|
|
||||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||||
|
|
||||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||||
|
|
||||||
<file alias="256x256/citra.png">icons/256x256/citra.png</file>
|
<file alias="256x256/citra.png">icons/256x256/citra.png</file>
|
||||||
|
|
||||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
<qresource prefix="default">
|
||||||
|
<file>style.qss</file>
|
||||||
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
13
dist/qt_themes/default/style.qss
vendored
Normal file
13
dist/qt_themes/default/style.qss
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
QPushButton#GraphicsAPIStatusBarButton {
|
||||||
|
color: #656565;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0px 3px 0px 3px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 60px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#GraphicsAPIStatusBarButton:hover {
|
||||||
|
border: 1px solid #76797C;
|
||||||
|
}
|
21
dist/qt_themes/qdarkstyle/style.qss
vendored
21
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -522,13 +522,12 @@ QToolButton#qt_toolbar_ext_button {
|
|||||||
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
color: #eff0f1;
|
color: #eff0f1;
|
||||||
border-width: 1px;
|
border: 1px solid #54575B;
|
||||||
border-color: #54575B;
|
|
||||||
border-style: solid;
|
|
||||||
padding: 6px 4px;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
padding: 5px 0px 5px 0px;
|
||||||
outline: none;
|
outline: none;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
min-height: 13px;
|
||||||
background-color: #232629;
|
background-color: #232629;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1237,3 +1236,17 @@ QPlainTextEdit:disabled {
|
|||||||
TouchScreenPreview {
|
TouchScreenPreview {
|
||||||
qproperty-dotHighlightColor: #3daee9;
|
qproperty-dotHighlightColor: #3daee9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPushButton#GraphicsAPIStatusBarButton {
|
||||||
|
color: #656565;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0px 3px 0px 3px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 60px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#GraphicsAPIStatusBarButton:hover {
|
||||||
|
border: 1px solid #76797C;
|
||||||
|
}
|
19
externals/CMakeLists.txt
vendored
19
externals/CMakeLists.txt
vendored
@ -60,6 +60,16 @@ endif()
|
|||||||
# Glad
|
# Glad
|
||||||
add_subdirectory(glad)
|
add_subdirectory(glad)
|
||||||
|
|
||||||
|
# glslang
|
||||||
|
set(SKIP_GLSLANG_INSTALL ON)
|
||||||
|
set(ENABLE_GLSLANG_BINARIES OFF)
|
||||||
|
set(ENABLE_SPVREMAPPER OFF)
|
||||||
|
set(ENABLE_CTEST OFF)
|
||||||
|
add_subdirectory(glslang)
|
||||||
|
|
||||||
|
# glm
|
||||||
|
add_subdirectory(glm)
|
||||||
|
|
||||||
# inih
|
# inih
|
||||||
add_subdirectory(inih)
|
add_subdirectory(inih)
|
||||||
|
|
||||||
@ -154,3 +164,12 @@ if(ANDROID)
|
|||||||
add_subdirectory(libyuv)
|
add_subdirectory(libyuv)
|
||||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# VMA
|
||||||
|
add_library(vma INTERFACE)
|
||||||
|
target_include_directories(vma INTERFACE ./vma)
|
||||||
|
|
||||||
|
# vulkan-headers
|
||||||
|
add_library(vulkan-headers INTERFACE)
|
||||||
|
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||||
|
|
||||||
|
1862
externals/glad/include/glad/glad.h
vendored
1862
externals/glad/include/glad/glad.h
vendored
File diff suppressed because it is too large
Load Diff
1037
externals/glad/src/glad.c
vendored
1037
externals/glad/src/glad.c
vendored
File diff suppressed because it is too large
Load Diff
1
externals/glm
vendored
Submodule
1
externals/glm
vendored
Submodule
Submodule externals/glm added at cc98465e35
1
externals/glslang
vendored
Submodule
1
externals/glslang
vendored
Submodule
Submodule externals/glslang added at c0cf8ad876
6
externals/microprofile/microprofile.h
vendored
6
externals/microprofile/microprofile.h
vendored
@ -847,7 +847,7 @@ inline MicroProfileLogEntry MicroProfileMakeLogIndex(uint64_t nBegin, MicroProfi
|
|||||||
MicroProfileLogEntry Entry = (nBegin<<62) | ((0x3fff&nToken)<<48) | (MP_LOG_TICK_MASK&nTick);
|
MicroProfileLogEntry Entry = (nBegin<<62) | ((0x3fff&nToken)<<48) | (MP_LOG_TICK_MASK&nTick);
|
||||||
int t = MicroProfileLogType(Entry);
|
int t = MicroProfileLogType(Entry);
|
||||||
uint64_t nTimerIndex = MicroProfileLogTimerIndex(Entry);
|
uint64_t nTimerIndex = MicroProfileLogTimerIndex(Entry);
|
||||||
MP_ASSERT(t == nBegin);
|
MP_ASSERT(static_cast<uint64_t>(t) == nBegin);
|
||||||
MP_ASSERT(nTimerIndex == (nToken&0x3fff));
|
MP_ASSERT(nTimerIndex == (nToken&0x3fff));
|
||||||
return Entry;
|
return Entry;
|
||||||
|
|
||||||
@ -1579,10 +1579,10 @@ void MicroProfileFlip()
|
|||||||
|
|
||||||
pFramePut->nFrameStartCpu = MP_TICK();
|
pFramePut->nFrameStartCpu = MP_TICK();
|
||||||
pFramePut->nFrameStartGpu = (uint32_t)MicroProfileGpuInsertTimeStamp();
|
pFramePut->nFrameStartGpu = (uint32_t)MicroProfileGpuInsertTimeStamp();
|
||||||
if(pFrameNext->nFrameStartGpu != (uint64_t)-1)
|
if(static_cast<uint64_t>(pFrameNext->nFrameStartGpu) != (uint64_t)-1)
|
||||||
pFrameNext->nFrameStartGpu = MicroProfileGpuGetTimeStamp((uint32_t)pFrameNext->nFrameStartGpu);
|
pFrameNext->nFrameStartGpu = MicroProfileGpuGetTimeStamp((uint32_t)pFrameNext->nFrameStartGpu);
|
||||||
|
|
||||||
if(pFrameCurrent->nFrameStartGpu == (uint64_t)-1)
|
if(static_cast<uint64_t>(pFrameCurrent->nFrameStartGpu) == (uint64_t)-1)
|
||||||
pFrameCurrent->nFrameStartGpu = pFrameNext->nFrameStartGpu + 1;
|
pFrameCurrent->nFrameStartGpu = pFrameNext->nFrameStartGpu + 1;
|
||||||
|
|
||||||
uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu;
|
uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu;
|
||||||
|
12
externals/microprofile/microprofileui.h
vendored
12
externals/microprofile/microprofileui.h
vendored
@ -354,7 +354,7 @@ void MicroProfileInitUI()
|
|||||||
if(!bInitialized)
|
if(!bInitialized)
|
||||||
{
|
{
|
||||||
bInitialized = true;
|
bInitialized = true;
|
||||||
memset(&g_MicroProfileUI, 0, sizeof(g_MicroProfileUI));
|
g_MicroProfileUI = {};
|
||||||
UI.nActiveMenu = UINT32_MAX;
|
UI.nActiveMenu = UINT32_MAX;
|
||||||
UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f;
|
UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f;
|
||||||
UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f;
|
UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f;
|
||||||
@ -845,8 +845,8 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
|
|||||||
MicroProfile& S = *MicroProfileGet();
|
MicroProfile& S = *MicroProfileGet();
|
||||||
MP_DEBUG_DUMP_RANGE();
|
MP_DEBUG_DUMP_RANGE();
|
||||||
int nY = nBaseY - UI.nOffsetY;
|
int nY = nBaseY - UI.nOffsetY;
|
||||||
int64_t nNumBoxes = 0;
|
[[maybe_unused]] int64_t nNumBoxes = 0;
|
||||||
int64_t nNumLines = 0;
|
[[maybe_unused]] int64_t nNumLines = 0;
|
||||||
|
|
||||||
uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;
|
uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;
|
||||||
MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent];
|
MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent];
|
||||||
@ -1988,7 +1988,7 @@ const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
nIndex = nIndex-1;
|
nIndex = nIndex-1;
|
||||||
if(nIndex < UI.GroupMenuCount)
|
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
|
||||||
{
|
{
|
||||||
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
|
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
|
||||||
static char buffer[MICROPROFILE_NAME_MAX_LEN+32];
|
static char buffer[MICROPROFILE_NAME_MAX_LEN+32];
|
||||||
@ -2135,7 +2135,7 @@ const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
|
|||||||
case 1: return "--";
|
case 1: return "--";
|
||||||
default:
|
default:
|
||||||
nIndex -= 2;
|
nIndex -= 2;
|
||||||
if(nIndex < UI.nCustomCount)
|
if(static_cast<uint32_t>(nIndex) < UI.nCustomCount)
|
||||||
{
|
{
|
||||||
return UI.Custom[nIndex].pName;
|
return UI.Custom[nIndex].pName;
|
||||||
}
|
}
|
||||||
@ -2185,7 +2185,7 @@ void MicroProfileUIClickGroups(int nIndex)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
nIndex -= 1;
|
nIndex -= 1;
|
||||||
if(nIndex < UI.GroupMenuCount)
|
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
|
||||||
{
|
{
|
||||||
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
|
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
|
||||||
if(Item.nIsCategory)
|
if(Item.nIsCategory)
|
||||||
|
2
externals/sdl2/CMakeLists.txt
vendored
2
externals/sdl2/CMakeLists.txt
vendored
@ -39,9 +39,9 @@ set(SDL_JOYSTICK ON CACHE BOOL "")
|
|||||||
set(SDL_HAPTIC OFF CACHE BOOL "")
|
set(SDL_HAPTIC OFF CACHE BOOL "")
|
||||||
set(SDL_HIDAPI ON CACHE BOOL "")
|
set(SDL_HIDAPI ON CACHE BOOL "")
|
||||||
set(SDL_POWER OFF CACHE BOOL "")
|
set(SDL_POWER OFF CACHE BOOL "")
|
||||||
set(SDL_THREADS ON CACHE BOOL "")
|
|
||||||
set(SDL_TIMERS ON CACHE BOOL "")
|
set(SDL_TIMERS ON CACHE BOOL "")
|
||||||
set(SDL_FILE ON CACHE BOOL "")
|
set(SDL_FILE ON CACHE BOOL "")
|
||||||
|
set(SDL_THREADS ON CACHE BOOL "")
|
||||||
set(SDL_LOADSO ON CACHE BOOL "")
|
set(SDL_LOADSO ON CACHE BOOL "")
|
||||||
set(SDL_CPUINFO OFF CACHE BOOL "")
|
set(SDL_CPUINFO OFF CACHE BOOL "")
|
||||||
set(SDL_FILESYSTEM OFF CACHE BOOL "")
|
set(SDL_FILESYSTEM OFF CACHE BOOL "")
|
||||||
|
19670
externals/vma/vk_mem_alloc.h
vendored
Normal file
19670
externals/vma/vk_mem_alloc.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
externals/vulkan-headers
vendored
Submodule
1
externals/vulkan-headers
vendored
Submodule
Submodule externals/vulkan-headers added at 98f440ce68
@ -103,6 +103,7 @@ else()
|
|||||||
|
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||||
|
add_compile_options("-Wa,-mbig-obj")
|
||||||
if (COMPILE_WITH_DWARF)
|
if (COMPILE_WITH_DWARF)
|
||||||
add_compile_options("-gdwarf")
|
add_compile_options("-gdwarf")
|
||||||
endif()
|
endif()
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
Config::Config() {
|
Config::Config() {
|
||||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
// 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);
|
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
@ -38,8 +38,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
|||||||
if (sdl2_config->ParseError() < 0) {
|
if (sdl2_config->ParseError() < 0) {
|
||||||
if (retry) {
|
if (retry) {
|
||||||
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
|
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
|
||||||
FileUtil::CreateFullPath(location);
|
Common::FS::CreateFullPath(location);
|
||||||
FileUtil::WriteStringToFile(true, location, default_contents);
|
Common::FS::WriteStringToFile(true, location, default_contents);
|
||||||
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
|
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
|
||||||
|
|
||||||
return LoadINI(default_contents, false);
|
return LoadINI(default_contents, false);
|
||||||
|
@ -35,7 +35,7 @@ std::vector<u8> GetSMDHData(std::string physical_name) {
|
|||||||
std::string update_path = Service::AM::GetTitleContentPath(
|
std::string update_path = Service::AM::GetTitleContentPath(
|
||||||
Service::FS::MediaType::SDMC, program_id + 0x0000000E'00000000);
|
Service::FS::MediaType::SDMC, program_id + 0x0000000E'00000000);
|
||||||
|
|
||||||
if (!FileUtil::Exists(update_path))
|
if (!Common::FS::Exists(update_path))
|
||||||
return original_smdh;
|
return original_smdh;
|
||||||
|
|
||||||
std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);
|
std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);
|
||||||
|
@ -159,9 +159,9 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||||
Log::SetGlobalFilter(log_filter);
|
Log::SetGlobalFilter(log_filter);
|
||||||
Log::AddBackend(std::make_unique<Log::LogcatBackend>());
|
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>(
|
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");
|
LOG_INFO(Frontend, "Logging backend initialised");
|
||||||
|
|
||||||
// Initialize misc classes
|
// Initialize misc classes
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
#include "core/frontend/applets/default_applets.h"
|
#include "core/frontend/applets/default_applets.h"
|
||||||
#include "core/frontend/camera/factory.h"
|
#include "core/frontend/camera/factory.h"
|
||||||
#include "core/frontend/mic.h"
|
#include "core/frontend/mic.h"
|
||||||
#include "core/frontend/scope_acquire_context.h"
|
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/hle/service/nfc/nfc.h"
|
#include "core/hle/service/nfc/nfc.h"
|
||||||
#include "core/savestate.h"
|
#include "core/savestate.h"
|
||||||
@ -155,7 +154,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
Config{};
|
Config{};
|
||||||
// Replace with game-specific settings
|
// Replace with game-specific settings
|
||||||
u64 program_id{};
|
u64 program_id{};
|
||||||
FileUtil::SetCurrentRomPath(filepath);
|
Common::FS::SetCurrentRomPath(filepath);
|
||||||
auto app_loader = Loader::GetLoader(filepath);
|
auto app_loader = Loader::GetLoader(filepath);
|
||||||
if (app_loader) {
|
if (app_loader) {
|
||||||
app_loader->ReadProgramId(program_id);
|
app_loader->ReadProgramId(program_id);
|
||||||
@ -306,18 +305,18 @@ void Java_org_citra_citra_1emu_NativeLibrary_SwapScreens(JNIEnv* env, [[maybe_un
|
|||||||
void Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env,
|
||||||
[[maybe_unused]] jclass clazz,
|
[[maybe_unused]] jclass clazz,
|
||||||
jstring j_directory) {
|
jstring j_directory) {
|
||||||
FileUtil::SetCurrentDir(GetJString(env, j_directory));
|
Common::FS::SetCurrentDir(GetJString(env, j_directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(
|
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(
|
||||||
JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
||||||
std::vector<std::string> games;
|
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) {
|
[&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) {
|
||||||
std::string path = directory + virtual_name;
|
std::string path = directory + virtual_name;
|
||||||
if (FileUtil::IsDirectory(path)) {
|
if (Common::FS::IsDirectory(path)) {
|
||||||
path += '/';
|
path += '/';
|
||||||
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
|
Common::FS::ForeachDirectoryEntry(nullptr, path, ScanDir);
|
||||||
} else {
|
} else {
|
||||||
auto loader = Loader::GetLoader(path);
|
auto loader = Loader::GetLoader(path);
|
||||||
if (loader) {
|
if (loader) {
|
||||||
@ -331,12 +330,12 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
ScanDir(nullptr, "",
|
ScanDir(nullptr, "",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
|
||||||
"Nintendo "
|
"Nintendo "
|
||||||
"3DS/00000000000000000000000000000000/"
|
"3DS/00000000000000000000000000000000/"
|
||||||
"00000000000000000000000000000000/title/00040000");
|
"00000000000000000000000000000000/title/00040000");
|
||||||
ScanDir(nullptr, "",
|
ScanDir(nullptr, "",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
|
||||||
"00000000000000000000000000000000/title/00040010");
|
"00000000000000000000000000000000/title/00040010");
|
||||||
jobjectArray jgames = env->NewObjectArray(static_cast<jsize>(games.size()),
|
jobjectArray jgames = env->NewObjectArray(static_cast<jsize>(games.size()),
|
||||||
env->FindClass("java/lang/String"), nullptr);
|
env->FindClass("java/lang/String"), nullptr);
|
||||||
|
@ -43,8 +43,8 @@ FuncDL<int(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, const uint8_
|
|||||||
FuncDL<void(AVCodecParserContext*)> av_parser_close_dl;
|
FuncDL<void(AVCodecParserContext*)> av_parser_close_dl;
|
||||||
|
|
||||||
bool InitFFmpegDL() {
|
bool InitFFmpegDL() {
|
||||||
std::string dll_path = FileUtil::GetUserPath(FileUtil::UserPath::DLLDir);
|
std::string dll_path = Common::FS::GetUserPath(Common::FS::UserPath::DLLDir);
|
||||||
FileUtil::CreateDir(dll_path);
|
Common::FS::CreateDir(dll_path);
|
||||||
std::wstring w_dll_path = Common::UTF8ToUTF16W(dll_path);
|
std::wstring w_dll_path = Common::UTF8ToUTF16W(dll_path);
|
||||||
SetDllDirectoryW(w_dll_path.c_str());
|
SetDllDirectoryW(w_dll_path.c_str());
|
||||||
|
|
||||||
|
@ -7,17 +7,7 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
||||||
#include "common/microprofile.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// windows.h needs to be included before shellapi.h
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <shellapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "citra/config.h"
|
#include "citra/config.h"
|
||||||
#include "citra/emu_window/emu_window_sdl2.h"
|
#include "citra/emu_window/emu_window_sdl2.h"
|
||||||
#include "citra/lodepng_image_interface.h"
|
#include "citra/lodepng_image_interface.h"
|
||||||
@ -25,21 +15,17 @@
|
|||||||
#include "common/detached_tasks.h"
|
#include "common/detached_tasks.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/filter.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/microprofile.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/dumping/backend.h"
|
#include "core/dumping/backend.h"
|
||||||
#include "core/file_sys/cia_container.h"
|
|
||||||
#include "core/frontend/applets/default_applets.h"
|
#include "core/frontend/applets/default_applets.h"
|
||||||
#include "core/frontend/framebuffer_layout.h"
|
#include "core/frontend/framebuffer_layout.h"
|
||||||
#include "core/frontend/scope_acquire_context.h"
|
|
||||||
#include "core/gdbstub/gdbstub.h"
|
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/hle/service/cfg/cfg.h"
|
#include "core/hle/service/cfg/cfg.h"
|
||||||
#include "core/loader/loader.h"
|
|
||||||
#include "core/movie.h"
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
@ -52,6 +38,11 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
// windows.h needs to be included before shellapi.h
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
|
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
|
||||||
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
||||||
@ -104,35 +95,35 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
|
|||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::CouldNotConnect:
|
case Network::RoomMember::Error::CouldNotConnect:
|
||||||
LOG_ERROR(Network, "Error: Could not connect");
|
LOG_ERROR(Network, "Error: Could not connect");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::NameCollision:
|
case Network::RoomMember::Error::NameCollision:
|
||||||
LOG_ERROR(
|
LOG_ERROR(
|
||||||
Network,
|
Network,
|
||||||
"You tried to use the same nickname as another user that is connected to the Room");
|
"You tried to use the same nickname as another user that is connected to the Room");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::MacCollision:
|
case Network::RoomMember::Error::MacCollision:
|
||||||
LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
|
LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
|
||||||
"connected to the Room");
|
"connected to the Room");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::ConsoleIdCollision:
|
case Network::RoomMember::Error::ConsoleIdCollision:
|
||||||
LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
|
LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::WrongPassword:
|
case Network::RoomMember::Error::WrongPassword:
|
||||||
LOG_ERROR(Network, "Room replied with: Wrong password");
|
LOG_ERROR(Network, "Room replied with: Wrong password");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::WrongVersion:
|
case Network::RoomMember::Error::WrongVersion:
|
||||||
LOG_ERROR(Network,
|
LOG_ERROR(Network,
|
||||||
"You are using a different version than the room you are trying to connect to");
|
"You are using a different version than the room you are trying to connect to");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::RoomIsFull:
|
case Network::RoomMember::Error::RoomIsFull:
|
||||||
LOG_ERROR(Network, "The room is full");
|
LOG_ERROR(Network, "The room is full");
|
||||||
exit(1);
|
std::exit(1);
|
||||||
break;
|
break;
|
||||||
case Network::RoomMember::Error::HostKicked:
|
case Network::RoomMember::Error::HostKicked:
|
||||||
LOG_ERROR(Network, "You have been kicked by the host");
|
LOG_ERROR(Network, "You have been kicked by the host");
|
||||||
@ -140,6 +131,8 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
|
|||||||
case Network::RoomMember::Error::HostBanned:
|
case Network::RoomMember::Error::HostBanned:
|
||||||
LOG_ERROR(Network, "You have been banned by the host");
|
LOG_ERROR(Network, "You have been banned by the host");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Network, "Unknown network error {}", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +170,8 @@ static void InitializeLogging() {
|
|||||||
|
|
||||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||||
|
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
|
||||||
FileUtil::CreateFullPath(log_dir);
|
Common::FS::CreateFullPath(log_dir);
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||||
@ -359,7 +352,7 @@ int main(int argc, char** argv) {
|
|||||||
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
||||||
|
|
||||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
||||||
Frontend::ScopeAcquireContext scope(*emu_window);
|
const auto scope = emu_window->Acquire();
|
||||||
Core::System& system = Core::System::GetInstance();
|
Core::System& system = Core::System::GetInstance();
|
||||||
|
|
||||||
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
|
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
Config::Config() {
|
Config::Config() {
|
||||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
// 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);
|
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
@ -35,8 +35,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
|||||||
if (sdl2_config->ParseError() < 0) {
|
if (sdl2_config->ParseError() < 0) {
|
||||||
if (retry) {
|
if (retry) {
|
||||||
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
|
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
|
||||||
FileUtil::CreateFullPath(location);
|
Common::FS::CreateFullPath(location);
|
||||||
FileUtil::WriteStringToFile(true, location, default_contents);
|
Common::FS::WriteStringToFile(true, location, default_contents);
|
||||||
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
|
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
|
||||||
|
|
||||||
return LoadINI(default_contents, false);
|
return LoadINI(default_contents, false);
|
||||||
@ -109,7 +109,8 @@ void Config::ReadValues() {
|
|||||||
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
|
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
|
||||||
|
|
||||||
// Renderer
|
// Renderer
|
||||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
|
Settings::values.graphics_api =
|
||||||
|
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 0));
|
||||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||||
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@ -207,9 +208,9 @@ void Config::ReadValues() {
|
|||||||
sdl2_config->GetBoolean("Data Storage", "use_custom_storage", false);
|
sdl2_config->GetBoolean("Data Storage", "use_custom_storage", false);
|
||||||
|
|
||||||
if (Settings::values.use_custom_storage) {
|
if (Settings::values.use_custom_storage) {
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
|
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
|
||||||
sdl2_config->GetString("Data Storage", "nand_directory", ""));
|
sdl2_config->GetString("Data Storage", "nand_directory", ""));
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir,
|
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
|
||||||
sdl2_config->GetString("Data Storage", "sdmc_directory", ""));
|
sdl2_config->GetString("Data Storage", "sdmc_directory", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,8 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
|||||||
|
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
|
|
||||||
if (Settings::values.use_gles) {
|
const bool is_opengles = Settings::values.graphics_api == Settings::GraphicsAPI::OpenGLES;
|
||||||
|
if (is_opengles) {
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
@ -201,7 +202,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
auto gl_load_func = is_opengles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||||
|
|
||||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
||||||
|
@ -260,9 +260,13 @@ endif()
|
|||||||
create_target_directory_groups(citra-qt)
|
create_target_directory_groups(citra-qt)
|
||||||
|
|
||||||
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
|
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
|
||||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia)
|
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia)
|
||||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||||
|
|
||||||
|
if (NOT WIN32)
|
||||||
|
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(citra-qt PRIVATE
|
target_compile_definitions(citra-qt PRIVATE
|
||||||
# Use QStringBuilder for string concatenation to reduce
|
# Use QStringBuilder for string concatenation to reduce
|
||||||
# the overall number of temporary strings created.
|
# the overall number of temporary strings created.
|
||||||
|
@ -54,12 +54,12 @@ QtKeyboardDialog::QtKeyboardDialog(QWidget* parent, QtKeyboard* keyboard_)
|
|||||||
case ButtonConfig::None:
|
case ButtonConfig::None:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
connect(buttons, &QDialogButtonBox::accepted, this, [=] { Submit(); });
|
connect(buttons, &QDialogButtonBox::accepted, this, [this] { Submit(); });
|
||||||
connect(buttons, &QDialogButtonBox::rejected, this, [=] {
|
connect(buttons, &QDialogButtonBox::rejected, this, [this] {
|
||||||
button = QtKeyboard::cancel_id;
|
button = QtKeyboard::cancel_id;
|
||||||
accept();
|
accept();
|
||||||
});
|
});
|
||||||
connect(buttons, &QDialogButtonBox::helpRequested, this, [=] {
|
connect(buttons, &QDialogButtonBox::helpRequested, this, [this] {
|
||||||
button = QtKeyboard::forgot_id;
|
button = QtKeyboard::forgot_id;
|
||||||
accept();
|
accept();
|
||||||
});
|
});
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QOffscreenSurface>
|
#include <QOffscreenSurface>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
#include <QOpenGLFunctions>
|
#include <QOpenGLExtraFunctions>
|
||||||
#include <QOpenGLFunctions_4_3_Core>
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "citra_qt/bootmanager.h"
|
#include "citra_qt/bootmanager.h"
|
||||||
#include "citra_qt/main.h"
|
#include "citra_qt/main.h"
|
||||||
@ -17,56 +17,60 @@
|
|||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "core/3ds.h"
|
#include "core/3ds.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/frontend/scope_acquire_context.h"
|
|
||||||
#include "core/perf_stats.h"
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
#include "input_common/motion_emu.h"
|
#include "input_common/motion_emu.h"
|
||||||
#include "network/network.h"
|
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
#if !defined(WIN32)
|
||||||
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
|
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
|
||||||
|
|
||||||
EmuThread::~EmuThread() = default;
|
EmuThread::~EmuThread() = default;
|
||||||
|
|
||||||
static GMainWindow* GetMainWindow() {
|
static GMainWindow* GetMainWindow() {
|
||||||
for (QWidget* w : qApp->topLevelWidgets()) {
|
const auto widgets = qApp->topLevelWidgets();
|
||||||
|
for (QWidget* w : widgets) {
|
||||||
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
|
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
|
||||||
return main;
|
return main;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::run() {
|
void EmuThread::run() {
|
||||||
MicroProfileOnThreadCreate("EmuThread");
|
MicroProfileOnThreadCreate("EmuThread");
|
||||||
Frontend::ScopeAcquireContext scope(core_context);
|
const auto scope = core_context.Acquire();
|
||||||
|
|
||||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||||
|
|
||||||
Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
|
Core::System& system = Core::System::GetInstance();
|
||||||
|
system.Renderer().Rasterizer()->LoadDiskResources(
|
||||||
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||||
emit LoadProgress(stage, value, total);
|
emit LoadProgress(stage, value, total);
|
||||||
});
|
});
|
||||||
|
|
||||||
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||||
|
emit HideLoadingScreen();
|
||||||
|
|
||||||
core_context.MakeCurrent();
|
core_context.MakeCurrent();
|
||||||
|
|
||||||
if (Core::System::GetInstance().frame_limiter.IsFrameAdvancing()) {
|
if (system.frame_limiter.IsFrameAdvancing()) {
|
||||||
// Usually the loading screen is hidden after the first frame is drawn. In this case
|
// Usually the loading screen is hidden after the first frame is drawn. In this case
|
||||||
// we hide it immediately as we need to wait for user input to start the emulation.
|
// we hide it immediately as we need to wait for user input to start the emulation.
|
||||||
emit HideLoadingScreen();
|
emit HideLoadingScreen();
|
||||||
Core::System::GetInstance().frame_limiter.WaitOnce();
|
system.frame_limiter.WaitOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Holds whether the cpu was running during the last iteration,
|
// Holds whether the cpu was running during the last iteration,
|
||||||
// so that the DebugModeLeft signal can be emitted before the
|
// so that the DebugModeLeft signal can be emitted before the
|
||||||
// next execution step.
|
// next execution step.
|
||||||
bool was_active = false;
|
bool was_active = false;
|
||||||
Core::System& system = Core::System::GetInstance();
|
|
||||||
while (!stop_run) {
|
while (!stop_run) {
|
||||||
if (running) {
|
if (running) {
|
||||||
if (!was_active)
|
if (!was_active)
|
||||||
@ -111,87 +115,234 @@ void EmuThread::run() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
|
class OpenGLSharedContext : public Frontend::GraphicsContext {
|
||||||
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
public:
|
||||||
event_handler(event_handler) {
|
/// Create the original context that should be shared from
|
||||||
|
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
|
||||||
|
QSurfaceFormat format;
|
||||||
|
|
||||||
// disable vsync for any shared contexts
|
format.setVersion(4, 4);
|
||||||
auto format = shared_context->format();
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||||
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
|
|
||||||
this->setFormat(format);
|
|
||||||
|
|
||||||
context->setShareContext(shared_context);
|
if (Settings::values.renderer_debug) {
|
||||||
context->setScreen(this->screen());
|
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||||
context->setFormat(format);
|
}
|
||||||
context->create();
|
|
||||||
|
|
||||||
setSurfaceType(QWindow::OpenGLSurface);
|
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||||
|
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||||
|
format.setSwapInterval(0);
|
||||||
|
|
||||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
context = std::make_unique<QOpenGLContext>();
|
||||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
context->setFormat(format);
|
||||||
}
|
if (!context->create()) {
|
||||||
|
LOG_ERROR(Frontend, "Unable to create main openGL context");
|
||||||
OpenGLWindow::~OpenGLWindow() {
|
}
|
||||||
context->doneCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLWindow::Present() {
|
|
||||||
if (!isExposed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
context->makeCurrent(this);
|
|
||||||
if (VideoCore::g_renderer) {
|
|
||||||
VideoCore::g_renderer->TryPresent(100);
|
|
||||||
}
|
}
|
||||||
context->swapBuffers(this);
|
|
||||||
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
|
||||||
f->glFinish();
|
|
||||||
QWindow::requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenGLWindow::event(QEvent* event) {
|
/// Create the shared contexts for rendering and presentation
|
||||||
switch (event->type()) {
|
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
|
||||||
case QEvent::UpdateRequest:
|
|
||||||
|
// disable vsync for any shared contexts
|
||||||
|
auto format = share_context->format();
|
||||||
|
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new : 0);
|
||||||
|
|
||||||
|
context = std::make_unique<QOpenGLContext>();
|
||||||
|
context->setShareContext(share_context);
|
||||||
|
context->setFormat(format);
|
||||||
|
if (!context->create()) {
|
||||||
|
LOG_ERROR(Frontend, "Unable to create shared openGL context");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!main_surface) {
|
||||||
|
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
|
||||||
|
offscreen_surface->setFormat(format);
|
||||||
|
offscreen_surface->create();
|
||||||
|
surface = offscreen_surface.get();
|
||||||
|
} else {
|
||||||
|
surface = main_surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~OpenGLSharedContext() {
|
||||||
|
context->doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapBuffers() override {
|
||||||
|
context->swapBuffers(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MakeCurrent() override {
|
||||||
|
// We can't track the current state of the underlying context in this wrapper class because
|
||||||
|
// Qt may make the underlying context not current for one reason or another. In particular,
|
||||||
|
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
|
||||||
|
// Instead of always just making the context current (which does not have any caching to
|
||||||
|
// check if the underlying context is already current) we can check for the current context
|
||||||
|
// in the thread local data by calling `currentContext()` and checking if its ours.
|
||||||
|
if (QOpenGLContext::currentContext() != context.get()) {
|
||||||
|
context->makeCurrent(surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoneCurrent() override {
|
||||||
|
context->doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
QOpenGLContext* GetShareContext() const {
|
||||||
|
return context.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Avoid using Qt parent system here since we might move the QObjects to new threads
|
||||||
|
// As a note, this means we should avoid using slots/signals with the objects too
|
||||||
|
std::unique_ptr<QOpenGLContext> context;
|
||||||
|
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||||
|
QSurface* surface;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DummyContext : public Frontend::GraphicsContext {};
|
||||||
|
|
||||||
|
class RenderWidget : public QWidget {
|
||||||
|
public:
|
||||||
|
RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
|
||||||
|
setAttribute(Qt::WA_NativeWindow);
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~RenderWidget() = default;
|
||||||
|
|
||||||
|
virtual void Present() {}
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent* event) override {
|
||||||
Present();
|
Present();
|
||||||
return true;
|
update();
|
||||||
case QEvent::MouseButtonPress:
|
|
||||||
case QEvent::MouseButtonRelease:
|
|
||||||
case QEvent::MouseButtonDblClick:
|
|
||||||
case QEvent::MouseMove:
|
|
||||||
case QEvent::KeyPress:
|
|
||||||
case QEvent::KeyRelease:
|
|
||||||
case QEvent::FocusIn:
|
|
||||||
case QEvent::FocusOut:
|
|
||||||
case QEvent::FocusAboutToChange:
|
|
||||||
case QEvent::Enter:
|
|
||||||
case QEvent::Leave:
|
|
||||||
case QEvent::Wheel:
|
|
||||||
case QEvent::TabletMove:
|
|
||||||
case QEvent::TabletPress:
|
|
||||||
case QEvent::TabletRelease:
|
|
||||||
case QEvent::TabletEnterProximity:
|
|
||||||
case QEvent::TabletLeaveProximity:
|
|
||||||
case QEvent::TouchBegin:
|
|
||||||
case QEvent::TouchUpdate:
|
|
||||||
case QEvent::TouchEnd:
|
|
||||||
case QEvent::InputMethodQuery:
|
|
||||||
case QEvent::TouchCancel:
|
|
||||||
return QCoreApplication::sendEvent(event_handler, event);
|
|
||||||
case QEvent::Drop:
|
|
||||||
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
|
|
||||||
return true;
|
|
||||||
case QEvent::DragEnter:
|
|
||||||
case QEvent::DragMove:
|
|
||||||
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return QWindow::event(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resizeEvent(QResizeEvent* ev) override {
|
||||||
|
render_window->resize(ev->size());
|
||||||
|
render_window->OnFramebufferSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent* event) override {
|
||||||
|
InputCommon::GetKeyboard()->PressKey(event->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyReleaseEvent(QKeyEvent* event) override {
|
||||||
|
InputCommon::GetKeyboard()->ReleaseKey(event->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent* event) override {
|
||||||
|
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||||
|
return; // touch input is handled in TouchBeginEvent
|
||||||
|
|
||||||
|
const auto pos{event->pos()};
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
const auto [x, y] = render_window->ScaleTouch(pos);
|
||||||
|
render_window->TouchPressed(x, y);
|
||||||
|
} else if (event->button() == Qt::RightButton) {
|
||||||
|
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mouseMoveEvent(QMouseEvent* event) override {
|
||||||
|
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||||
|
return; // touch input is handled in TouchUpdateEvent
|
||||||
|
|
||||||
|
const auto pos{event->pos()};
|
||||||
|
const auto [x, y] = render_window->ScaleTouch(pos);
|
||||||
|
render_window->TouchMoved(x, y);
|
||||||
|
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
void mouseReleaseEvent(QMouseEvent* event) override {
|
||||||
|
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||||
|
return; // touch input is handled in TouchEndEvent
|
||||||
|
|
||||||
|
if (event->button() == Qt::LeftButton)
|
||||||
|
render_window->TouchReleased();
|
||||||
|
else if (event->button() == Qt::RightButton)
|
||||||
|
InputCommon::GetMotionEmu()->EndTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<unsigned, unsigned> GetSize() const {
|
||||||
|
return std::make_pair(width(), height());
|
||||||
|
}
|
||||||
|
|
||||||
|
QPaintEngine* paintEngine() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GRenderWindow* render_window;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenGLRenderWidget : public RenderWidget {
|
||||||
|
public:
|
||||||
|
explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
|
||||||
|
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetContext(std::unique_ptr<OpenGLSharedContext>&& context_) {
|
||||||
|
context = std::move(context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Present() override {
|
||||||
|
if (!isVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Core::System::GetInstance().IsPoweredOn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context->MakeCurrent();
|
||||||
|
const auto f = context->GetShareContext()->extraFunctions();
|
||||||
|
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
VideoCore::g_renderer->TryPresent(100);
|
||||||
|
context->SwapBuffers();
|
||||||
|
f->glFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<OpenGLSharedContext> context{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class VulkanRenderWidget : public RenderWidget {
|
||||||
|
public:
|
||||||
|
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
|
||||||
|
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Frontend::WindowSystemType GetWindowSystemType() {
|
||||||
|
// Determine WSI type based on Qt platform.
|
||||||
|
QString platform_name = QGuiApplication::platformName();
|
||||||
|
if (platform_name == QStringLiteral("windows"))
|
||||||
|
return Frontend::WindowSystemType::Windows;
|
||||||
|
else if (platform_name == QStringLiteral("xcb"))
|
||||||
|
return Frontend::WindowSystemType::X11;
|
||||||
|
else if (platform_name == QStringLiteral("wayland"))
|
||||||
|
return Frontend::WindowSystemType::Wayland;
|
||||||
|
|
||||||
|
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
|
||||||
|
return Frontend::WindowSystemType::Windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLWindow::exposeEvent(QExposeEvent* event) {
|
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
|
||||||
QWindow::requestUpdate();
|
Frontend::EmuWindow::WindowSystemInfo wsi;
|
||||||
QWindow::exposeEvent(event);
|
wsi.type = GetWindowSystemType();
|
||||||
|
|
||||||
|
// Our Win32 Qt external doesn't have the private API.
|
||||||
|
#if defined(WIN32) || defined(__APPLE__)
|
||||||
|
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
|
||||||
|
#else
|
||||||
|
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||||
|
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||||
|
if (wsi.type == Frontend::WindowSystemType::Wayland)
|
||||||
|
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
|
||||||
|
else
|
||||||
|
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
|
||||||
|
#endif
|
||||||
|
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
|
||||||
|
|
||||||
|
return wsi;
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
||||||
@ -218,11 +369,11 @@ GRenderWindow::~GRenderWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::MakeCurrent() {
|
void GRenderWindow::MakeCurrent() {
|
||||||
core_context->MakeCurrent();
|
main_context->MakeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::DoneCurrent() {
|
void GRenderWindow::DoneCurrent() {
|
||||||
core_context->DoneCurrent();
|
main_context->DoneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::PollEvents() {
|
void GRenderWindow::PollEvents() {
|
||||||
@ -387,33 +538,70 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
|||||||
OnFramebufferSizeChanged();
|
OnFramebufferSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::InitRenderTarget() {
|
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||||
|
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api;
|
||||||
|
if (graphics_api == Settings::GraphicsAPI::OpenGL ||
|
||||||
|
graphics_api == Settings::GraphicsAPI::OpenGLES) {
|
||||||
|
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
|
||||||
|
// Bind the shared contexts to the main surface in case the backend wants to take over
|
||||||
|
// presentation
|
||||||
|
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
|
||||||
|
child_widget->windowHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<DummyContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GRenderWindow::InitRenderTarget() {
|
||||||
ReleaseRenderTarget();
|
ReleaseRenderTarget();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create a dummy render widget so that Qt
|
||||||
|
// places the render window at the correct position.
|
||||||
|
const RenderWidget dummy_widget{this};
|
||||||
|
}
|
||||||
|
|
||||||
first_frame = false;
|
first_frame = false;
|
||||||
|
|
||||||
GMainWindow* parent = GetMainWindow();
|
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api;
|
||||||
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
switch (graphics_api) {
|
||||||
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
|
case Settings::GraphicsAPI::OpenGL:
|
||||||
child_window->create();
|
case Settings::GraphicsAPI::OpenGLES:
|
||||||
child_widget = createWindowContainer(child_window, this);
|
if (!InitializeOpenGL()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Settings::GraphicsAPI::Vulkan:
|
||||||
|
if (!InitializeVulkan()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Window System information with the new render target
|
||||||
|
window_info = GetWindowSystemInfo(child_widget->windowHandle());
|
||||||
|
|
||||||
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||||
|
|
||||||
layout()->addWidget(child_widget);
|
layout()->addWidget(child_widget);
|
||||||
|
// Reset minimum required size to avoid resizing issues on the main window after restarting.
|
||||||
|
setMinimumSize(1, 1);
|
||||||
|
|
||||||
core_context = CreateSharedContext();
|
|
||||||
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||||
OnFramebufferSizeChanged();
|
OnFramebufferSizeChanged();
|
||||||
BackupGeometry();
|
BackupGeometry();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::ReleaseRenderTarget() {
|
void GRenderWindow::ReleaseRenderTarget() {
|
||||||
if (child_widget) {
|
if (child_widget) {
|
||||||
layout()->removeWidget(child_widget);
|
layout()->removeWidget(child_widget);
|
||||||
delete child_widget;
|
child_widget->deleteLater();
|
||||||
child_widget = nullptr;
|
child_widget = nullptr;
|
||||||
}
|
}
|
||||||
|
main_context.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||||
@ -423,7 +611,7 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
|
|||||||
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||||
VideoCore::RequestScreenshot(
|
VideoCore::RequestScreenshot(
|
||||||
screenshot_image.bits(),
|
screenshot_image.bits(),
|
||||||
[=] {
|
[this, &screenshot_path] {
|
||||||
const std::string std_screenshot_path = screenshot_path.toStdString();
|
const std::string std_screenshot_path = screenshot_path.toStdString();
|
||||||
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
|
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
|
||||||
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
|
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
|
||||||
@ -438,6 +626,29 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
|
|||||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GRenderWindow::InitializeOpenGL() {
|
||||||
|
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||||
|
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||||
|
auto child = new OpenGLRenderWidget(this);
|
||||||
|
child_widget = child;
|
||||||
|
child_widget->windowHandle()->create();
|
||||||
|
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
|
||||||
|
main_context = context;
|
||||||
|
child->SetContext(
|
||||||
|
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GRenderWindow::InitializeVulkan() {
|
||||||
|
auto child = new VulkanRenderWidget(this);
|
||||||
|
child_widget = child;
|
||||||
|
child_widget->windowHandle()->create();
|
||||||
|
main_context = std::make_unique<DummyContext>();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
||||||
this->emu_thread = emu_thread;
|
this->emu_thread = emu_thread;
|
||||||
}
|
}
|
||||||
@ -449,31 +660,3 @@ void GRenderWindow::OnEmulationStopping() {
|
|||||||
void GRenderWindow::showEvent(QShowEvent* event) {
|
void GRenderWindow::showEvent(QShowEvent* event) {
|
||||||
QWidget::showEvent(event);
|
QWidget::showEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
|
||||||
return std::make_unique<GLContext>(QOpenGLContext::globalShareContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
GLContext::GLContext(QOpenGLContext* shared_context)
|
|
||||||
: context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
|
||||||
surface(std::make_unique<QOffscreenSurface>(nullptr)) {
|
|
||||||
|
|
||||||
// disable vsync for any shared contexts
|
|
||||||
auto format = shared_context->format();
|
|
||||||
format.setSwapInterval(0);
|
|
||||||
|
|
||||||
context->setShareContext(shared_context);
|
|
||||||
context->setFormat(format);
|
|
||||||
context->create();
|
|
||||||
surface->setParent(shared_context->parent());
|
|
||||||
surface->setFormat(format);
|
|
||||||
surface->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::MakeCurrent() {
|
|
||||||
context->makeCurrent(surface.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::DoneCurrent() {
|
|
||||||
context->doneCurrent();
|
|
||||||
}
|
|
||||||
|
@ -27,19 +27,6 @@ namespace VideoCore {
|
|||||||
enum class LoadCallbackStage;
|
enum class LoadCallbackStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GLContext : public Frontend::GraphicsContext {
|
|
||||||
public:
|
|
||||||
explicit GLContext(QOpenGLContext* shared_context);
|
|
||||||
|
|
||||||
void MakeCurrent() override;
|
|
||||||
|
|
||||||
void DoneCurrent() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<QOpenGLContext> context;
|
|
||||||
std::unique_ptr<QOffscreenSurface> surface;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EmuThread final : public QThread {
|
class EmuThread final : public QThread {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -126,24 +113,6 @@ signals:
|
|||||||
void HideLoadingScreen();
|
void HideLoadingScreen();
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpenGLWindow : public QWindow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
|
|
||||||
|
|
||||||
~OpenGLWindow();
|
|
||||||
|
|
||||||
void Present();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool event(QEvent* event) override;
|
|
||||||
void exposeEvent(QExposeEvent* event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<QOpenGLContext> context;
|
|
||||||
QWidget* event_handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -179,13 +148,15 @@ public:
|
|||||||
|
|
||||||
void focusOutEvent(QFocusEvent* event) override;
|
void focusOutEvent(QFocusEvent* event) override;
|
||||||
|
|
||||||
void InitRenderTarget();
|
bool InitRenderTarget();
|
||||||
|
|
||||||
/// Destroy the previous run's child_widget which should also destroy the child_window
|
/// Destroy the previous run's child_widget which should also destroy the child_window
|
||||||
void ReleaseRenderTarget();
|
void ReleaseRenderTarget();
|
||||||
|
|
||||||
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
||||||
|
|
||||||
|
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void OnEmulationStarting(EmuThread* emu_thread);
|
void OnEmulationStarting(EmuThread* emu_thread);
|
||||||
@ -205,29 +176,29 @@ signals:
|
|||||||
void MouseActivity();
|
void MouseActivity();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
|
||||||
void TouchBeginEvent(const QTouchEvent* event);
|
void TouchBeginEvent(const QTouchEvent* event);
|
||||||
void TouchUpdateEvent(const QTouchEvent* event);
|
void TouchUpdateEvent(const QTouchEvent* event);
|
||||||
void TouchEndEvent();
|
void TouchEndEvent();
|
||||||
|
|
||||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||||
|
|
||||||
std::unique_ptr<GraphicsContext> core_context;
|
bool InitializeOpenGL();
|
||||||
|
bool InitializeVulkan();
|
||||||
QByteArray geometry;
|
|
||||||
|
|
||||||
/// Native window handle that backs this presentation widget
|
|
||||||
QWindow* child_window = nullptr;
|
|
||||||
|
|
||||||
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
|
|
||||||
/// put the child_window into a widget then add it to the layout. This child_widget can be
|
|
||||||
/// parented to GRenderWindow and use Qt's lifetime system
|
|
||||||
QWidget* child_widget = nullptr;
|
|
||||||
|
|
||||||
EmuThread* emu_thread;
|
EmuThread* emu_thread;
|
||||||
|
|
||||||
|
// Main context that will be shared with all other contexts that are requested.
|
||||||
|
// If this is used in a shared context setting, then this should not be used directly, but
|
||||||
|
// should instead be shared from
|
||||||
|
std::shared_ptr<Frontend::GraphicsContext> main_context;
|
||||||
|
|
||||||
/// Temporary storage of the screenshot taken
|
/// Temporary storage of the screenshot taken
|
||||||
QImage screenshot_image;
|
QImage screenshot_image;
|
||||||
|
|
||||||
|
QByteArray geometry;
|
||||||
|
|
||||||
|
QWidget* child_widget = nullptr;
|
||||||
|
|
||||||
bool first_frame = false;
|
bool first_frame = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -37,7 +37,7 @@ CheatDialog::CheatDialog(QWidget* parent)
|
|||||||
connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
|
connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
|
||||||
connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
|
connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
|
||||||
|
|
||||||
connect(ui->buttonSave, &QPushButton::clicked,
|
connect(ui->buttonSave, &QPushButton::clicked, this,
|
||||||
[this] { SaveCheat(ui->tableCheats->currentRow()); });
|
[this] { SaveCheat(ui->tableCheats->currentRow()); });
|
||||||
connect(ui->buttonDelete, &QPushButton::clicked, this, &CheatDialog::OnDeleteCheat);
|
connect(ui->buttonDelete, &QPushButton::clicked, this, &CheatDialog::OnDeleteCheat);
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ bool CheatDialog::SaveCheat(int row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the cheat lines are valid
|
// Check if the cheat lines are valid
|
||||||
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, QString::SkipEmptyParts);
|
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, Qt::SkipEmptyParts);
|
||||||
for (int i = 0; i < code_lines.size(); ++i) {
|
for (int i = 0; i < code_lines.size(); ++i) {
|
||||||
Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
|
Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
|
||||||
if (cheat_line.valid)
|
if (cheat_line.valid)
|
||||||
@ -190,8 +190,9 @@ void CheatDialog::OnDeleteCheat() {
|
|||||||
if (newly_created) {
|
if (newly_created) {
|
||||||
newly_created = false;
|
newly_created = false;
|
||||||
} else {
|
} else {
|
||||||
Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow());
|
auto& cheat_engine = Core::System::GetInstance().CheatEngine();
|
||||||
Core::System::GetInstance().CheatEngine().SaveCheatFile();
|
cheat_engine.RemoveCheat(ui->tableCheats->currentRow());
|
||||||
|
cheat_engine.SaveCheatFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadCheats();
|
LoadCheats();
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
Config::Config() {
|
Config::Config() {
|
||||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
// 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";
|
qt_config_loc = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "qt-config.ini";
|
||||||
FileUtil::CreateFullPath(qt_config_loc);
|
Common::FS::CreateFullPath(qt_config_loc);
|
||||||
qt_config =
|
qt_config =
|
||||||
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
|
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
|
||||||
Reload();
|
Reload();
|
||||||
@ -312,8 +312,8 @@ void Config::ReadDataStorageValues() {
|
|||||||
ReadSetting(QStringLiteral("sdmc_directory"), QStringLiteral("")).toString().toStdString();
|
ReadSetting(QStringLiteral("sdmc_directory"), QStringLiteral("")).toString().toStdString();
|
||||||
|
|
||||||
if (Settings::values.use_custom_storage) {
|
if (Settings::values.use_custom_storage) {
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, nand_dir);
|
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir, nand_dir);
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, sdmc_dir);
|
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir, sdmc_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
@ -327,6 +327,9 @@ void Config::ReadDebuggingValues() {
|
|||||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||||
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
|
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
|
||||||
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
|
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
|
||||||
|
Settings::values.renderer_debug = ReadSetting(QStringLiteral("renderer_debug"), false).toBool();
|
||||||
|
Settings::values.dump_command_buffers =
|
||||||
|
ReadSetting(QStringLiteral("dump_command_buffers"), false).toBool();
|
||||||
|
|
||||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||||
for (const auto& service_module : Service::service_module_map) {
|
for (const auto& service_module : Service::service_module_map) {
|
||||||
@ -478,6 +481,11 @@ void Config::ReadPathValues() {
|
|||||||
void Config::ReadRendererValues() {
|
void Config::ReadRendererValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||||
|
|
||||||
|
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(
|
||||||
|
ReadSetting(QStringLiteral("graphics_api"), static_cast<u32>(Settings::GraphicsAPI::OpenGL))
|
||||||
|
.toUInt());
|
||||||
|
Settings::values.physical_device = ReadSetting(QStringLiteral("physical_device"), 0).toUInt();
|
||||||
|
Settings::values.async_command_recording = ReadSetting(QStringLiteral("async_command_recording"), true).toBool();
|
||||||
Settings::values.use_hw_renderer =
|
Settings::values.use_hw_renderer =
|
||||||
ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
|
ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
|
||||||
Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
|
Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
|
||||||
@ -517,7 +525,7 @@ void Config::ReadRendererValues() {
|
|||||||
void Config::ReadShortcutValues() {
|
void Config::ReadShortcutValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Shortcuts"));
|
qt_config->beginGroup(QStringLiteral("Shortcuts"));
|
||||||
|
|
||||||
for (auto [name, group, shortcut] : default_hotkeys) {
|
for (const auto& [name, group, shortcut] : default_hotkeys) {
|
||||||
auto [keyseq, context] = shortcut;
|
auto [keyseq, context] = shortcut;
|
||||||
qt_config->beginGroup(group);
|
qt_config->beginGroup(group);
|
||||||
qt_config->beginGroup(name);
|
qt_config->beginGroup(name);
|
||||||
@ -552,7 +560,7 @@ void Config::ReadSystemValues() {
|
|||||||
// https://developers.google.com/media/vp9/live-encoding
|
// https://developers.google.com/media/vp9/live-encoding
|
||||||
const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
|
const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
|
||||||
QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
|
QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
|
||||||
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QString{};
|
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QStringLiteral("");
|
||||||
|
|
||||||
void Config::ReadVideoDumpingValues() {
|
void Config::ReadVideoDumpingValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("VideoDumping"));
|
qt_config->beginGroup(QStringLiteral("VideoDumping"));
|
||||||
@ -867,10 +875,10 @@ void Config::SaveDataStorageValues() {
|
|||||||
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
|
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
|
||||||
WriteSetting(QStringLiteral("use_custom_storage"), Settings::values.use_custom_storage, false);
|
WriteSetting(QStringLiteral("use_custom_storage"), Settings::values.use_custom_storage, false);
|
||||||
WriteSetting(QStringLiteral("nand_directory"),
|
WriteSetting(QStringLiteral("nand_directory"),
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)),
|
||||||
QStringLiteral(""));
|
QStringLiteral(""));
|
||||||
WriteSetting(QStringLiteral("sdmc_directory"),
|
WriteSetting(QStringLiteral("sdmc_directory"),
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)),
|
||||||
QStringLiteral(""));
|
QStringLiteral(""));
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
@ -883,6 +891,9 @@ void Config::SaveDebuggingValues() {
|
|||||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||||
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
|
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
|
||||||
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
|
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
|
||||||
|
WriteSetting(QStringLiteral("renderer_debug"), Settings::values.renderer_debug, false);
|
||||||
|
WriteSetting(QStringLiteral("dump_command_buffers"), Settings::values.dump_command_buffers,
|
||||||
|
false);
|
||||||
|
|
||||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||||
for (const auto& service_module : Settings::values.lle_modules) {
|
for (const auto& service_module : Settings::values.lle_modules) {
|
||||||
@ -991,6 +1002,10 @@ void Config::SavePathValues() {
|
|||||||
void Config::SaveRendererValues() {
|
void Config::SaveRendererValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||||
|
|
||||||
|
WriteSetting(QStringLiteral("graphics_api"), static_cast<u32>(Settings::values.graphics_api),
|
||||||
|
static_cast<u32>(Settings::GraphicsAPI::OpenGL));
|
||||||
|
WriteSetting(QStringLiteral("physical_device"), Settings::values.physical_device, 0);
|
||||||
|
WriteSetting(QStringLiteral("async_command_recording"), Settings::values.async_command_recording, true);
|
||||||
WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
|
WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
|
||||||
WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
|
WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@ -1012,9 +1027,9 @@ void Config::SaveRendererValues() {
|
|||||||
200);
|
200);
|
||||||
|
|
||||||
// Cast to double because Qt's written float values are not human-readable
|
// Cast to double because Qt's written float values are not human-readable
|
||||||
WriteSetting(QStringLiteral("bg_red"), (double)Settings::values.bg_red, 0.0);
|
WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
|
||||||
WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0);
|
WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
|
||||||
WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0);
|
WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
|
||||||
|
|
||||||
WriteSetting(QStringLiteral("texture_filter_name"),
|
WriteSetting(QStringLiteral("texture_filter_name"),
|
||||||
QString::fromStdString(Settings::values.texture_filter_name),
|
QString::fromStdString(Settings::values.texture_filter_name),
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "citra_qt/configuration/configure_camera.h"
|
#include "citra_qt/configuration/configure_camera.h"
|
||||||
#include "citra_qt/uisettings.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/frontend/camera/factory.h"
|
#include "core/frontend/camera/factory.h"
|
||||||
#include "core/frontend/camera/interface.h"
|
#include "core/frontend/camera/interface.h"
|
||||||
#include "core/hle/service/cam/cam.h"
|
#include "core/hle/service/cam/cam.h"
|
||||||
@ -91,7 +89,7 @@ void ConfigureCamera::ConnectEvents() {
|
|||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
});
|
});
|
||||||
connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked);
|
connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked);
|
||||||
connect(ui->preview_button, &QPushButton::clicked, this, [=] { StartPreviewing(); });
|
connect(ui->preview_button, &QPushButton::clicked, this, [this] { StartPreviewing(); });
|
||||||
connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) {
|
connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) {
|
||||||
ui->camera_file->setDisabled(state == Qt::Checked);
|
ui->camera_file->setDisabled(state == Qt::Checked);
|
||||||
ui->toolButton->setDisabled(state == Qt::Checked);
|
ui->toolButton->setDisabled(state == Qt::Checked);
|
||||||
@ -99,12 +97,11 @@ void ConfigureCamera::ConnectEvents() {
|
|||||||
ui->camera_file->setText(QString{});
|
ui->camera_file->setText(QString{});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(ui->camera_file, &QLineEdit::textChanged, this, [=] { StopPreviewing(); });
|
connect(ui->camera_file, &QLineEdit::textChanged, this, [this] { StopPreviewing(); });
|
||||||
connect(ui->system_camera,
|
connect(ui->system_camera, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
[this] { StopPreviewing(); });
|
||||||
[=] { StopPreviewing(); });
|
connect(ui->camera_flip, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
connect(ui->camera_flip, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
[this] { StopPreviewing(); });
|
||||||
this, [=] { StopPreviewing(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCamera::UpdateCameraMode() {
|
void ConfigureCamera::UpdateCameraMode() {
|
||||||
|
@ -4,16 +4,17 @@
|
|||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QMessageBox>
|
||||||
#include "citra_qt/configuration/configure_debug.h"
|
#include "citra_qt/configuration/configure_debug.h"
|
||||||
#include "citra_qt/debugger/console.h"
|
#include "citra_qt/debugger/console.h"
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/backend.h"
|
|
||||||
#include "common/logging/filter.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
#include "qcheckbox.h"
|
||||||
#include "ui_configure_debug.h"
|
#include "ui_configure_debug.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
|
||||||
ConfigureDebug::ConfigureDebug(QWidget* parent)
|
ConfigureDebug::ConfigureDebug(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureDebug>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureDebug>()) {
|
||||||
@ -21,10 +22,44 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
|
|||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
|
||||||
connect(ui->open_log_button, &QPushButton::clicked, []() {
|
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));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
});
|
});
|
||||||
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
|
||||||
|
connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) {
|
||||||
|
if (checked && Settings::values.graphics_api == Settings::GraphicsAPI::Vulkan) {
|
||||||
|
try {
|
||||||
|
Vulkan::Instance debug_inst{true};
|
||||||
|
} catch (vk::LayerNotPresentError& err) {
|
||||||
|
ui->toggle_renderer_debug->toggle();
|
||||||
|
QMessageBox::warning(
|
||||||
|
this, tr("Validation layer not available"),
|
||||||
|
tr("Unable to enable debug renderer because the layer "
|
||||||
|
"<strong>VK_LAYER_KHRONOS_validation</strong> is missing. "
|
||||||
|
"Please install the Vulkan SDK or the appropriate package of your distribution"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->toggle_dump_command_buffers, &QCheckBox::clicked, this, [this](bool checked) {
|
||||||
|
if (checked && Settings::values.graphics_api == Settings::GraphicsAPI::Vulkan) {
|
||||||
|
try {
|
||||||
|
Vulkan::Instance debug_inst{false, true};
|
||||||
|
} catch (vk::LayerNotPresentError& err) {
|
||||||
|
ui->toggle_dump_command_buffers->toggle();
|
||||||
|
QMessageBox::warning(
|
||||||
|
this, tr("Command buffer dumping not available"),
|
||||||
|
tr("Unable to enable command buffer dumping because the layer "
|
||||||
|
"<strong>VK_LAYER_LUNARG_api_dump</strong> is missing. "
|
||||||
|
"Please install the Vulkan SDK or the appropriate package of your distribution"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
|
||||||
|
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
|
||||||
|
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
|
||||||
|
ui->toggle_dump_command_buffers->setEnabled(!is_powered_on);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureDebug::~ConfigureDebug() = default;
|
ConfigureDebug::~ConfigureDebug() = default;
|
||||||
@ -37,6 +72,8 @@ void ConfigureDebug::SetConfiguration() {
|
|||||||
ui->toggle_console->setChecked(UISettings::values.show_console);
|
ui->toggle_console->setChecked(UISettings::values.show_console);
|
||||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
|
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
|
||||||
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||||
|
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug);
|
||||||
|
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureDebug::ApplyConfiguration() {
|
void ConfigureDebug::ApplyConfiguration() {
|
||||||
@ -49,6 +86,8 @@ void ConfigureDebug::ApplyConfiguration() {
|
|||||||
filter.ParseFilterString(Settings::values.log_filter);
|
filter.ParseFilterString(Settings::values.log_filter);
|
||||||
Log::SetGlobalFilter(filter);
|
Log::SetGlobalFilter(filter);
|
||||||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||||
|
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
|
||||||
|
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureDebug::RetranslateUI() {
|
void ConfigureDebug::RetranslateUI() {
|
||||||
|
@ -22,5 +22,6 @@ public:
|
|||||||
void RetranslateUI();
|
void RetranslateUI();
|
||||||
void SetConfiguration();
|
void SetConfiguration();
|
||||||
|
|
||||||
|
private:
|
||||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>443</width>
|
<width>454</width>
|
||||||
<height>300</height>
|
<height>356</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -114,11 +114,31 @@
|
|||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toggle_cpu_jit">
|
<widget class="QCheckBox" name="toggle_cpu_jit">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable CPU JIT</string>
|
<string>Enable CPU JIT</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_renderer_debug">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Enables debug reporting in the currently selected graphics API. Causes measurable performance loss, don't enable unless for debugging purposes</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable debug renderer</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_dump_command_buffers">
|
||||||
|
<property name="text">
|
||||||
|
<string>Dump command buffers</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -117,9 +117,9 @@ void ConfigureGeneral::SetConfiguration() {
|
|||||||
QString screenshot_path = UISettings::values.screenshot_path;
|
QString screenshot_path = UISettings::values.screenshot_path;
|
||||||
if (screenshot_path.isEmpty()) {
|
if (screenshot_path.isEmpty()) {
|
||||||
screenshot_path =
|
screenshot_path =
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir));
|
||||||
screenshot_path.append(QStringLiteral("screenshots/"));
|
screenshot_path.append(QStringLiteral("screenshots/"));
|
||||||
FileUtil::CreateFullPath(screenshot_path.toStdString());
|
Common::FS::CreateFullPath(screenshot_path.toStdString());
|
||||||
UISettings::values.screenshot_path = screenshot_path;
|
UISettings::values.screenshot_path = screenshot_path;
|
||||||
}
|
}
|
||||||
ui->screenshot_dir_path->setText(screenshot_path);
|
ui->screenshot_dir_path->setText(screenshot_path);
|
||||||
@ -134,7 +134,7 @@ void ConfigureGeneral::ResetDefaults() {
|
|||||||
if (answer == QMessageBox::No)
|
if (answer == QMessageBox::No)
|
||||||
return;
|
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);
|
std::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,15 +10,28 @@
|
|||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "ui_configure_graphics.h"
|
#include "ui_configure_graphics.h"
|
||||||
#include "video_core/renderer_opengl/post_processing_opengl.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
|
||||||
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
DiscoverPhysicalDevices();
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
|
||||||
ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked());
|
const bool not_running = !Core::System::GetInstance().IsPoweredOn();
|
||||||
ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
const bool hw_renderer_enabled = ui->toggle_hw_renderer->isChecked();
|
||||||
|
ui->toggle_hw_renderer->setEnabled(not_running);
|
||||||
|
ui->hw_renderer_group->setEnabled(hw_renderer_enabled && not_running);
|
||||||
|
ui->toggle_vsync_new->setEnabled(not_running);
|
||||||
|
ui->graphics_api_combo->setEnabled(not_running);
|
||||||
|
ui->toggle_shader_jit->setEnabled(not_running);
|
||||||
|
ui->toggle_disk_shader_cache->setEnabled(hw_renderer_enabled && not_running);
|
||||||
|
ui->toggle_async_recording->setEnabled(hw_renderer_enabled && not_running);
|
||||||
|
ui->physical_device_combo->setEnabled(not_running);
|
||||||
|
SetPhysicalDeviceComboVisibility(ui->graphics_api_combo->currentIndex());
|
||||||
|
|
||||||
|
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
|
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
|
||||||
|
|
||||||
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
|
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
|
||||||
auto checked = ui->toggle_hw_renderer->isChecked();
|
auto checked = ui->toggle_hw_renderer->isChecked();
|
||||||
@ -31,7 +44,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
|||||||
ui->toggle_hw_shader->isChecked());
|
ui->toggle_hw_shader->isChecked());
|
||||||
|
|
||||||
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
|
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
|
||||||
auto checked = ui->toggle_hw_shader->isChecked();
|
const bool checked = ui->toggle_hw_shader->isChecked();
|
||||||
ui->hw_shader_group->setEnabled(checked);
|
ui->hw_shader_group->setEnabled(checked);
|
||||||
ui->toggle_disk_shader_cache->setEnabled(checked);
|
ui->toggle_disk_shader_cache->setEnabled(checked);
|
||||||
});
|
});
|
||||||
@ -69,6 +82,9 @@ void ConfigureGraphics::SetConfiguration() {
|
|||||||
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
|
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
|
||||||
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
|
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
|
||||||
ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new);
|
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() {
|
void ConfigureGraphics::ApplyConfiguration() {
|
||||||
@ -79,8 +95,30 @@ void ConfigureGraphics::ApplyConfiguration() {
|
|||||||
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
|
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
|
||||||
Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
|
Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
|
||||||
Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked();
|
Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked();
|
||||||
|
Settings::values.graphics_api =
|
||||||
|
static_cast<Settings::GraphicsAPI>(ui->graphics_api_combo->currentIndex());
|
||||||
|
Settings::values.physical_device = static_cast<u16>(ui->physical_device_combo->currentIndex());
|
||||||
|
Settings::values.async_command_recording = ui->toggle_async_recording->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureGraphics::RetranslateUI() {
|
void ConfigureGraphics::RetranslateUI() {
|
||||||
ui->retranslateUi(this);
|
ui->retranslateUi(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureGraphics::DiscoverPhysicalDevices() {
|
||||||
|
Vulkan::Instance instance{};
|
||||||
|
const auto physical_devices = instance.GetPhysicalDevices();
|
||||||
|
|
||||||
|
ui->physical_device_combo->clear();
|
||||||
|
for (const vk::PhysicalDevice& physical_device : physical_devices) {
|
||||||
|
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
|
||||||
|
ui->physical_device_combo->addItem(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
|
||||||
|
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
||||||
|
const bool is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
|
||||||
|
ui->physical_device_label->setVisible(is_visible);
|
||||||
|
ui->physical_device_combo->setVisible(is_visible);
|
||||||
|
}
|
||||||
|
@ -24,6 +24,11 @@ public:
|
|||||||
|
|
||||||
void UpdateBackgroundColorButton(const QColor& color);
|
void UpdateBackgroundColorButton(const QColor& color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DiscoverPhysicalDevices();
|
||||||
|
void SetPhysicalDeviceComboVisibility(int index);
|
||||||
|
|
||||||
|
private:
|
||||||
std::unique_ptr<Ui::ConfigureGraphics> ui;
|
std::unique_ptr<Ui::ConfigureGraphics> ui;
|
||||||
QColor bg_color;
|
QColor bg_color;
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,59 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="apiBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>API Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="graphics_api_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Graphics API</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="graphics_api_combo">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>OpenGL</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>OpenGLES</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Vulkan</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="physical_device_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Physical device</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="physical_device_combo"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="rendererBox">
|
<widget class="QGroupBox" name="rendererBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -118,6 +171,16 @@
|
|||||||
<string>Advanced</string>
|
<string>Advanced</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_async_recording">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>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,</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Async Command Recording</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toggle_disk_shader_cache">
|
<widget class="QCheckBox" name="toggle_disk_shader_cache">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
@ -192,10 +192,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
if (!button_map[button_id])
|
if (!button_map[button_id])
|
||||||
continue;
|
continue;
|
||||||
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
|
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(button_map[button_id], &QPushButton::clicked, [=]() {
|
connect(button_map[button_id], &QPushButton::clicked, [this, button_id]() {
|
||||||
HandleClick(
|
HandleClick(
|
||||||
button_map[button_id],
|
button_map[button_id],
|
||||||
[=](const Common::ParamPackage& params) {
|
[this, button_id](const Common::ParamPackage& params) {
|
||||||
buttons_param[button_id] = params;
|
buttons_param[button_id] = params;
|
||||||
// If the user closes the dialog, the changes are reverted in
|
// If the user closes the dialog, the changes are reverted in
|
||||||
// `GMainWindow::OnConfigure()`
|
// `GMainWindow::OnConfigure()`
|
||||||
@ -204,16 +204,16 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
},
|
},
|
||||||
InputCommon::Polling::DeviceType::Button);
|
InputCommon::Polling::DeviceType::Button);
|
||||||
});
|
});
|
||||||
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
|
connect(button_map[button_id], &QPushButton::customContextMenuRequested, this,
|
||||||
[=](const QPoint& menu_location) {
|
[this, button_id](const QPoint& menu_location) {
|
||||||
QMenu context_menu;
|
QMenu context_menu;
|
||||||
context_menu.addAction(tr("Clear"), [&] {
|
context_menu.addAction(tr("Clear"), this, [&] {
|
||||||
buttons_param[button_id].Clear();
|
buttons_param[button_id].Clear();
|
||||||
button_map[button_id]->setText(tr("[not set]"));
|
button_map[button_id]->setText(tr("[not set]"));
|
||||||
ApplyConfiguration();
|
ApplyConfiguration();
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
});
|
});
|
||||||
context_menu.addAction(tr("Restore Default"), [&] {
|
context_menu.addAction(tr("Restore Default"), this, [&] {
|
||||||
buttons_param[button_id] = Common::ParamPackage{
|
buttons_param[button_id] = Common::ParamPackage{
|
||||||
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
|
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
|
||||||
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
|
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
|
||||||
@ -230,27 +230,29 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
continue;
|
continue;
|
||||||
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
|
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
|
||||||
Qt::CustomContextMenu);
|
Qt::CustomContextMenu);
|
||||||
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, [=]() {
|
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, this,
|
||||||
HandleClick(
|
[this, analog_id, sub_button_id]() {
|
||||||
analog_map_buttons[analog_id][sub_button_id],
|
HandleClick(
|
||||||
[=](const Common::ParamPackage& params) {
|
analog_map_buttons[analog_id][sub_button_id],
|
||||||
SetAnalogButton(params, analogs_param[analog_id],
|
[this, analog_id, sub_button_id](const Common::ParamPackage& params) {
|
||||||
analog_sub_buttons[sub_button_id]);
|
SetAnalogButton(params, analogs_param[analog_id],
|
||||||
ApplyConfiguration();
|
analog_sub_buttons[sub_button_id]);
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
ApplyConfiguration();
|
||||||
},
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
InputCommon::Polling::DeviceType::Button);
|
},
|
||||||
});
|
InputCommon::Polling::DeviceType::Button);
|
||||||
|
});
|
||||||
connect(analog_map_buttons[analog_id][sub_button_id],
|
connect(analog_map_buttons[analog_id][sub_button_id],
|
||||||
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
|
&QPushButton::customContextMenuRequested, this,
|
||||||
|
[this, analog_id, sub_button_id](const QPoint& menu_location) {
|
||||||
QMenu context_menu;
|
QMenu context_menu;
|
||||||
context_menu.addAction(tr("Clear"), [&] {
|
context_menu.addAction(tr("Clear"), this, [&] {
|
||||||
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
|
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
|
||||||
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
|
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
|
||||||
ApplyConfiguration();
|
ApplyConfiguration();
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
});
|
});
|
||||||
context_menu.addAction(tr("Restore Default"), [&] {
|
context_menu.addAction(tr("Restore Default"), this, [&] {
|
||||||
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
|
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
|
||||||
Config::default_analogs[analog_id][sub_button_id])};
|
Config::default_analogs[analog_id][sub_button_id])};
|
||||||
SetAnalogButton(params, analogs_param[analog_id],
|
SetAnalogButton(params, analogs_param[analog_id],
|
||||||
@ -264,7 +266,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
menu_location));
|
menu_location));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
connect(analog_map_stick[analog_id], &QPushButton::clicked, [=]() {
|
connect(analog_map_stick[analog_id], &QPushButton::clicked, this, [this, analog_id]() {
|
||||||
if (QMessageBox::information(
|
if (QMessageBox::information(
|
||||||
this, tr("Information"),
|
this, tr("Information"),
|
||||||
tr("After pressing OK, first move your joystick horizontally, "
|
tr("After pressing OK, first move your joystick horizontally, "
|
||||||
@ -272,7 +274,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
||||||
HandleClick(
|
HandleClick(
|
||||||
analog_map_stick[analog_id],
|
analog_map_stick[analog_id],
|
||||||
[=](const Common::ParamPackage& params) {
|
[this, analog_id](const Common::ParamPackage& params) {
|
||||||
analogs_param[analog_id] = params;
|
analogs_param[analog_id] = params;
|
||||||
ApplyConfiguration();
|
ApplyConfiguration();
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
@ -280,29 +282,31 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
InputCommon::Polling::DeviceType::Analog);
|
InputCommon::Polling::DeviceType::Analog);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
|
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, this,
|
||||||
const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
|
[this, analog_id] {
|
||||||
const auto engine = analogs_param[analog_id].Get("engine", "");
|
const int slider_value =
|
||||||
if (engine == "sdl" || engine == "gcpad") {
|
analog_map_deadzone_and_modifier_slider[analog_id]->value();
|
||||||
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
|
const auto engine = analogs_param[analog_id].Get("engine", "");
|
||||||
tr("Deadzone: %1%").arg(slider_value));
|
if (engine == "sdl" || engine == "gcpad") {
|
||||||
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
|
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
|
||||||
} else {
|
tr("Deadzone: %1%").arg(slider_value));
|
||||||
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
|
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
|
||||||
tr("Modifier Scale: %1%").arg(slider_value));
|
} else {
|
||||||
analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
|
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
|
||||||
}
|
tr("Modifier Scale: %1%").arg(slider_value));
|
||||||
ApplyConfiguration();
|
analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
}
|
||||||
});
|
ApplyConfiguration();
|
||||||
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Circle Mod button is common for both the sticks, so update the modifier settings
|
// The Circle Mod button is common for both the sticks, so update the modifier settings
|
||||||
// for both the sticks.
|
// for both the sticks.
|
||||||
connect(ui->buttonCircleMod, &QPushButton::clicked, [=]() {
|
connect(ui->buttonCircleMod, &QPushButton::clicked, this, [this]() {
|
||||||
HandleClick(
|
HandleClick(
|
||||||
ui->buttonCircleMod,
|
ui->buttonCircleMod,
|
||||||
[=](const Common::ParamPackage& params) {
|
[this](const Common::ParamPackage& params) {
|
||||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
||||||
analog_id++) {
|
analog_id++) {
|
||||||
SetAnalogButton(params, analogs_param[analog_id], "modifier");
|
SetAnalogButton(params, analogs_param[analog_id], "modifier");
|
||||||
@ -312,10 +316,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
},
|
},
|
||||||
InputCommon::Polling::DeviceType::Button);
|
InputCommon::Polling::DeviceType::Button);
|
||||||
});
|
});
|
||||||
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested,
|
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested, this,
|
||||||
[&](const QPoint& menu_location) {
|
[&](const QPoint& menu_location) {
|
||||||
QMenu context_menu;
|
QMenu context_menu;
|
||||||
context_menu.addAction(tr("Clear"), [&] {
|
context_menu.addAction(tr("Clear"), this, [&] {
|
||||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
||||||
analog_id++) {
|
analog_id++) {
|
||||||
analogs_param[analog_id].Erase("modifier");
|
analogs_param[analog_id].Erase("modifier");
|
||||||
@ -325,7 +329,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
});
|
});
|
||||||
|
|
||||||
context_menu.addAction(tr("Restore Default"), [&] {
|
context_menu.addAction(tr("Restore Default"), this, [&] {
|
||||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
|
||||||
analog_id++) {
|
analog_id++) {
|
||||||
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
|
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
|
||||||
@ -341,7 +345,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location));
|
context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->buttonMotionTouch, &QPushButton::clicked, [this] {
|
connect(ui->buttonMotionTouch, &QPushButton::clicked, this, [this] {
|
||||||
QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
|
QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
|
||||||
return motion_touch_dialog->exec();
|
return motion_touch_dialog->exec();
|
||||||
});
|
});
|
||||||
@ -356,18 +360,17 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||||||
connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile);
|
connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile);
|
||||||
connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile);
|
connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile);
|
||||||
|
|
||||||
connect(ui->profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
connect(ui->profile, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int i) {
|
||||||
[this](int i) {
|
ApplyConfiguration();
|
||||||
ApplyConfiguration();
|
Settings::SaveProfile(Settings::values.current_input_profile_index);
|
||||||
Settings::SaveProfile(Settings::values.current_input_profile_index);
|
Settings::LoadProfile(i);
|
||||||
Settings::LoadProfile(i);
|
LoadConfiguration();
|
||||||
LoadConfiguration();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
timeout_timer->setSingleShot(true);
|
timeout_timer->setSingleShot(true);
|
||||||
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
|
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
|
||||||
|
|
||||||
connect(poll_timer.get(), &QTimer::timeout, [this]() {
|
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
|
||||||
Common::ParamPackage params;
|
Common::ParamPackage params;
|
||||||
for (auto& poller : device_pollers) {
|
for (auto& poller : device_pollers) {
|
||||||
params = poller->GetNextInput();
|
params = poller->GetNextInput();
|
||||||
@ -554,7 +557,7 @@ void ConfigureInput::AutoMap() {
|
|||||||
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
|
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
input_setter = [=](const Common::ParamPackage& params) {
|
input_setter = [this](const Common::ParamPackage& params) {
|
||||||
MapFromButton(params);
|
MapFromButton(params);
|
||||||
ApplyConfiguration();
|
ApplyConfiguration();
|
||||||
Settings::SaveProfile(ui->profile->currentIndex());
|
Settings::SaveProfile(ui->profile->currentIndex());
|
||||||
|
@ -46,6 +46,9 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
|
|||||||
case CalibrationConfigurationJob::Status::Completed:
|
case CalibrationConfigurationJob::Status::Completed:
|
||||||
text = tr("Configuration completed!");
|
text = tr("Configuration completed!");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Frontend, "Unknown calibration status {}", status);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
|
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
|
||||||
if (status == CalibrationConfigurationJob::Status::Completed) {
|
if (status == CalibrationConfigurationJob::Status::Completed) {
|
||||||
@ -99,9 +102,9 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
|
|||||||
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
|
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
|
||||||
|
|
||||||
timeout_timer->setSingleShot(true);
|
timeout_timer->setSingleShot(true);
|
||||||
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
|
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
|
||||||
|
|
||||||
connect(poll_timer.get(), &QTimer::timeout, [this]() {
|
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
|
||||||
Common::ParamPackage params;
|
Common::ParamPackage params;
|
||||||
for (auto& poller : device_pollers) {
|
for (auto& poller : device_pollers) {
|
||||||
params = poller->GetNextInput();
|
params = poller->GetNextInput();
|
||||||
@ -202,7 +205,7 @@ void ConfigureMotionTouch::ConnectEvents() {
|
|||||||
connect(ui->touch_provider,
|
connect(ui->touch_provider,
|
||||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||||
[this]([[maybe_unused]] int index) { UpdateUiDisplay(); });
|
[this]([[maybe_unused]] int index) { UpdateUiDisplay(); });
|
||||||
connect(ui->motion_controller_button, &QPushButton::clicked, [=]() {
|
connect(ui->motion_controller_button, &QPushButton::clicked, this, [this]() {
|
||||||
if (QMessageBox::information(this, tr("Information"),
|
if (QMessageBox::information(this, tr("Information"),
|
||||||
tr("After pressing OK, press a button on the controller whose "
|
tr("After pressing OK, press a button on the controller whose "
|
||||||
"motion you want to track."),
|
"motion you want to track."),
|
||||||
@ -210,7 +213,7 @@ void ConfigureMotionTouch::ConnectEvents() {
|
|||||||
ui->motion_controller_button->setText(tr("[press button]"));
|
ui->motion_controller_button->setText(tr("[press button]"));
|
||||||
ui->motion_controller_button->setFocus();
|
ui->motion_controller_button->setFocus();
|
||||||
|
|
||||||
input_setter = [=](const Common::ParamPackage& params) {
|
input_setter = [this](const Common::ParamPackage& params) {
|
||||||
guid = params.Get("guid", "0");
|
guid = params.Get("guid", "0");
|
||||||
port = params.Get("port", 0);
|
port = params.Get("port", 0);
|
||||||
};
|
};
|
||||||
|
@ -16,33 +16,33 @@ ConfigureStorage::ConfigureStorage(QWidget* parent)
|
|||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
|
||||||
connect(ui->open_nand_dir, &QPushButton::clicked, []() {
|
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));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->change_nand_dir, &QPushButton::clicked, this, [this]() {
|
connect(ui->change_nand_dir, &QPushButton::clicked, this, [this]() {
|
||||||
const QString dir_path = QFileDialog::getExistingDirectory(
|
const QString dir_path = QFileDialog::getExistingDirectory(
|
||||||
this, tr("Select NAND Directory"),
|
this, tr("Select NAND Directory"),
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)),
|
||||||
QFileDialog::ShowDirsOnly);
|
QFileDialog::ShowDirsOnly);
|
||||||
if (!dir_path.isEmpty()) {
|
if (!dir_path.isEmpty()) {
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, dir_path.toStdString());
|
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir, dir_path.toStdString());
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->open_sdmc_dir, &QPushButton::clicked, []() {
|
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));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->change_sdmc_dir, &QPushButton::clicked, this, [this]() {
|
connect(ui->change_sdmc_dir, &QPushButton::clicked, this, [this]() {
|
||||||
const QString dir_path = QFileDialog::getExistingDirectory(
|
const QString dir_path = QFileDialog::getExistingDirectory(
|
||||||
this, tr("Select SDMC Directory"),
|
this, tr("Select SDMC Directory"),
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)),
|
||||||
QFileDialog::ShowDirsOnly);
|
QFileDialog::ShowDirsOnly);
|
||||||
if (!dir_path.isEmpty()) {
|
if (!dir_path.isEmpty()) {
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, dir_path.toStdString());
|
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir, dir_path.toStdString());
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -61,13 +61,13 @@ ConfigureStorage::~ConfigureStorage() = default;
|
|||||||
|
|
||||||
void ConfigureStorage::SetConfiguration() {
|
void ConfigureStorage::SetConfiguration() {
|
||||||
ui->nand_group->setVisible(Settings::values.use_custom_storage);
|
ui->nand_group->setVisible(Settings::values.use_custom_storage);
|
||||||
QString nand_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
|
QString nand_path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
|
||||||
ui->nand_dir_path->setText(nand_path);
|
ui->nand_dir_path->setText(nand_path);
|
||||||
ui->open_nand_dir->setEnabled(!nand_path.isEmpty());
|
ui->open_nand_dir->setEnabled(!nand_path.isEmpty());
|
||||||
|
|
||||||
ui->sdmc_group->setVisible(Settings::values.use_virtual_sd &&
|
ui->sdmc_group->setVisible(Settings::values.use_virtual_sd &&
|
||||||
Settings::values.use_custom_storage);
|
Settings::values.use_custom_storage);
|
||||||
QString sdmc_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
|
QString sdmc_path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
|
||||||
ui->sdmc_dir_path->setText(sdmc_path);
|
ui->sdmc_dir_path->setText(sdmc_path);
|
||||||
ui->open_sdmc_dir->setEnabled(!sdmc_path.isEmpty());
|
ui->open_sdmc_dir->setEnabled(!sdmc_path.isEmpty());
|
||||||
|
|
||||||
@ -82,10 +82,10 @@ void ConfigureStorage::ApplyConfiguration() {
|
|||||||
Settings::values.use_custom_storage = ui->toggle_custom_storage->isChecked();
|
Settings::values.use_custom_storage = ui->toggle_custom_storage->isChecked();
|
||||||
|
|
||||||
if (!Settings::values.use_custom_storage) {
|
if (!Settings::values.use_custom_storage) {
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
|
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
|
||||||
GetDefaultUserPath(FileUtil::UserPath::NANDDir));
|
GetDefaultUserPath(Common::FS::UserPath::NANDDir));
|
||||||
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir,
|
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
|
||||||
GetDefaultUserPath(FileUtil::UserPath::SDMCDir));
|
GetDefaultUserPath(Common::FS::UserPath::SDMCDir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +508,7 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
|
|||||||
}
|
}
|
||||||
const auto pos = MapToDeviceCoords(event->x(), event->y());
|
const auto pos = MapToDeviceCoords(event->x(), event->y());
|
||||||
if (pos) {
|
if (pos) {
|
||||||
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
|
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x(), pos->y()));
|
||||||
} else {
|
} else {
|
||||||
coord_label->clear();
|
coord_label->clear();
|
||||||
}
|
}
|
||||||
@ -568,7 +568,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
|
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
|
||||||
if (coord_label) {
|
if (coord_label) {
|
||||||
coord_label->setText(
|
coord_label->setText(
|
||||||
QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
|
QStringLiteral("X: %1, Y: %2").arg(device_coord->x(), device_coord->y()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
|
#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
|
||||||
#include "citra_qt/debugger/graphics/graphics_breakpoints_p.h"
|
#include "citra_qt/debugger/graphics/graphics_breakpoints_p.h"
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
|
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
|
||||||
: QAbstractListModel(parent), context_weak(debug_context),
|
: QAbstractListModel(parent), context_weak(debug_context),
|
||||||
@ -60,12 +59,15 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
|
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
return 0;
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Qt::ItemFlags flags = Qt::ItemIsEnabled;
|
Qt::ItemFlags flags = Qt::ItemIsEnabled;
|
||||||
if (index.column() == 0)
|
if (index.column() == 0) {
|
||||||
flags |= Qt::ItemIsUserCheckable;
|
flags |= Qt::ItemIsUserCheckable;
|
||||||
|
}
|
||||||
|
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
|
#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
|
||||||
#include "citra_qt/util/spinbox.h"
|
|
||||||
#include "citra_qt/util/util.h"
|
#include "citra_qt/util/util.h"
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@ -130,7 +129,7 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
|
|||||||
COMMAND_IN_RANGE(command_id, texturing.texture1) ||
|
COMMAND_IN_RANGE(command_id, texturing.texture1) ||
|
||||||
COMMAND_IN_RANGE(command_id, texturing.texture2)) {
|
COMMAND_IN_RANGE(command_id, texturing.texture2)) {
|
||||||
|
|
||||||
unsigned texture_index;
|
[[maybe_unused]] u32 texture_index;
|
||||||
if (COMMAND_IN_RANGE(command_id, texturing.texture0)) {
|
if (COMMAND_IN_RANGE(command_id, texturing.texture0)) {
|
||||||
texture_index = 0;
|
texture_index = 0;
|
||||||
} else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) {
|
} else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
#include "citra_qt/util/spinbox.h"
|
#include "citra_qt/util/spinbox.h"
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hw/gpu.h"
|
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "video_core/pica_state.h"
|
#include "video_core/pica_state.h"
|
||||||
#include "video_core/regs_framebuffer.h"
|
#include "video_core/regs_framebuffer.h"
|
||||||
@ -34,12 +33,15 @@ void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
|||||||
if (!(event->buttons() & Qt::LeftButton))
|
if (!(event->buttons() & Qt::LeftButton))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (pixmap() == nullptr)
|
const QPixmap pixmap = this->pixmap(Qt::ReturnByValue);
|
||||||
|
if (pixmap.isNull()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (surface_widget)
|
if (surface_widget) {
|
||||||
surface_widget->Pick(event->x() * pixmap()->width() / width(),
|
surface_widget->Pick(event->x() * pixmap.width() / width(),
|
||||||
event->y() * pixmap()->height() / height());
|
event->y() * pixmap.height() / height());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
||||||
@ -314,57 +316,46 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
|
|||||||
case Format::RGBA8: {
|
case Format::RGBA8: {
|
||||||
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
|
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
|
||||||
.arg(QString::number(value.b(), 'f', 2))
|
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::RGB8: {
|
case Format::RGB8: {
|
||||||
auto value = Color::DecodeRGB8(pixel) / 255.0f;
|
auto value = Color::DecodeRGB8(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
|
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
QString::number(value.b(), 'f', 2));
|
||||||
.arg(QString::number(value.b(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::RGB5A1: {
|
case Format::RGB5A1: {
|
||||||
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
|
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
|
||||||
.arg(QString::number(value.b(), 'f', 2))
|
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::RGB565: {
|
case Format::RGB565: {
|
||||||
auto value = Color::DecodeRGB565(pixel) / 255.0f;
|
auto value = Color::DecodeRGB565(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
|
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
QString::number(value.b(), 'f', 2));
|
||||||
.arg(QString::number(value.b(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::RGBA4: {
|
case Format::RGBA4: {
|
||||||
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
|
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
|
||||||
.arg(QString::number(value.g(), 'f', 2))
|
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
|
||||||
.arg(QString::number(value.b(), 'f', 2))
|
|
||||||
.arg(QString::number(value.a(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::IA8:
|
case Format::IA8:
|
||||||
return QStringLiteral("Index: %1, Alpha: %2").arg(pixel[0]).arg(pixel[1]);
|
return QStringLiteral("Index: %1, Alpha: %2").arg(pixel[0], pixel[1]);
|
||||||
case Format::RG8: {
|
case Format::RG8: {
|
||||||
auto value = Color::DecodeRG8(pixel) / 255.0f;
|
auto value = Color::DecodeRG8(pixel) / 255.0f;
|
||||||
return QStringLiteral("Red: %1, Green: %2")
|
return QStringLiteral("Red: %1, Green: %2")
|
||||||
.arg(QString::number(value.r(), 'f', 2))
|
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2));
|
||||||
.arg(QString::number(value.g(), 'f', 2));
|
|
||||||
}
|
}
|
||||||
case Format::I8:
|
case Format::I8:
|
||||||
return QStringLiteral("Index: %1").arg(*pixel);
|
return QStringLiteral("Index: %1").arg(*pixel);
|
||||||
case Format::A8:
|
case Format::A8:
|
||||||
return QStringLiteral("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
|
return QStringLiteral("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
|
||||||
case Format::IA4:
|
case Format::IA4:
|
||||||
return QStringLiteral("Index: %1, Alpha: %2")
|
return QStringLiteral("Index: %1, Alpha: %2").arg(*pixel & 0xF, (*pixel & 0xF0) >> 4);
|
||||||
.arg(*pixel & 0xF)
|
|
||||||
.arg((*pixel & 0xF0) >> 4);
|
|
||||||
case Format::I4: {
|
case Format::I4: {
|
||||||
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
|
||||||
return QStringLiteral("Index: %1").arg(i);
|
return QStringLiteral("Index: %1").arg(i);
|
||||||
@ -390,8 +381,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
|
|||||||
case Format::X24S8: {
|
case Format::X24S8: {
|
||||||
auto values = Color::DecodeD24S8(pixel);
|
auto values = Color::DecodeD24S8(pixel);
|
||||||
return QStringLiteral("Depth: %1, Stencil: %2")
|
return QStringLiteral("Depth: %1, Stencil: %2")
|
||||||
.arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4))
|
.arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4), values[1]);
|
||||||
.arg(values[1]);
|
|
||||||
}
|
}
|
||||||
case Format::Unknown:
|
case Format::Unknown:
|
||||||
return QStringLiteral("Unknown format");
|
return QStringLiteral("Unknown format");
|
||||||
@ -401,8 +391,8 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QString nibbles;
|
QString nibbles;
|
||||||
for (unsigned i = 0; i < nibbles_per_pixel; i++) {
|
for (u32 i = 0; i < nibbles_per_pixel; i++) {
|
||||||
unsigned nibble_index = i;
|
u32 nibble_index = i;
|
||||||
if (nibble_mode) {
|
if (nibble_mode) {
|
||||||
nibble_index += (offset % 2) ? 0 : 1;
|
nibble_index += (offset % 2) ? 0 : 1;
|
||||||
}
|
}
|
||||||
@ -412,7 +402,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
surface_info_label->setText(
|
surface_info_label->setText(
|
||||||
QStringLiteral("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
|
QStringLiteral("Raw: 0x%3\n(%4)").arg(nibbles, GetText(surface_format, pixel)));
|
||||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,8 +666,8 @@ void GraphicsSurfaceWidget::SaveSurface() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selected_filter == png_filter) {
|
if (selected_filter == png_filter) {
|
||||||
const QPixmap* const pixmap = surface_picture_label->pixmap();
|
const QPixmap pixmap = surface_picture_label->pixmap(Qt::ReturnByValue);
|
||||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
ASSERT_MSG(!pixmap.isNull(), "No pixmap set");
|
||||||
|
|
||||||
QFile file{filename};
|
QFile file{filename};
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
@ -685,7 +675,7 @@ void GraphicsSurfaceWidget::SaveSurface() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pixmap->save(&file, "PNG")) {
|
if (!pixmap.save(&file, "PNG")) {
|
||||||
QMessageBox::warning(this, tr("Error"),
|
QMessageBox::warning(this, tr("Error"),
|
||||||
tr("Failed to save surface data to file '%1'").arg(filename));
|
tr("Failed to save surface data to file '%1'").arg(filename));
|
||||||
}
|
}
|
||||||
|
@ -111,13 +111,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Instruction instr = par->info.code[index.row()];
|
const Instruction& instr = par->info.code[index.row()];
|
||||||
const OpCode opcode = instr.opcode;
|
const OpCode opcode = instr.opcode;
|
||||||
const OpCode::Info opcode_info = opcode.GetInfo();
|
const OpCode::Info opcode_info = opcode.GetInfo();
|
||||||
const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd
|
const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd
|
||||||
? instr.mad.operand_desc_id.Value()
|
? instr.mad.operand_desc_id.Value()
|
||||||
: instr.common.operand_desc_id.Value();
|
: instr.common.operand_desc_id.Value();
|
||||||
const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern;
|
const SwizzlePattern& swizzle = par->info.swizzle_info[operand_desc_id].pattern;
|
||||||
|
|
||||||
// longest known instruction name: "setemit "
|
// longest known instruction name: "setemit "
|
||||||
int kOpcodeColumnWidth = 8;
|
int kOpcodeColumnWidth = 8;
|
||||||
@ -407,8 +407,8 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(
|
|||||||
static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
|
static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
|
||||||
input_data_mapper->setMapping(input_data[i], i);
|
input_data_mapper->setMapping(input_data[i], i);
|
||||||
}
|
}
|
||||||
connect(input_data_mapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped),
|
connect(input_data_mapper, &QSignalMapper::mappedInt, this,
|
||||||
this, &GraphicsVertexShaderWidget::OnInputAttributeChanged);
|
&GraphicsVertexShaderWidget::OnInputAttributeChanged);
|
||||||
|
|
||||||
auto main_widget = new QWidget;
|
auto main_widget = new QWidget;
|
||||||
auto main_layout = new QVBoxLayout;
|
auto main_layout = new QVBoxLayout;
|
||||||
@ -514,8 +514,10 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
|
|||||||
info.code.push_back({instr});
|
info.code.push_back({instr});
|
||||||
int num_attributes = shader_config.max_input_attribute_index + 1;
|
int num_attributes = shader_config.max_input_attribute_index + 1;
|
||||||
|
|
||||||
for (auto pattern : shader_setup.swizzle_data)
|
for (auto pattern : shader_setup.swizzle_data) {
|
||||||
info.swizzle_info.push_back({pattern});
|
const nihstro::SwizzleInfo swizzle_info = {.pattern = nihstro::SwizzlePattern{pattern}};
|
||||||
|
info.swizzle_info.push_back(swizzle_info);
|
||||||
|
}
|
||||||
|
|
||||||
u32 entry_point = Pica::g_state.regs.vs.main_offset;
|
u32 entry_point = Pica::g_state.regs.vs.main_offset;
|
||||||
info.labels.insert({entry_point, "main"});
|
info.labels.insert({entry_point, "main"});
|
||||||
|
@ -57,6 +57,7 @@ QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record
|
|||||||
return tr("HLE Unimplemented");
|
return tr("HLE Unimplemented");
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
return QLatin1String{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,8 @@ void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileWidget::wheelEvent(QWheelEvent* event) {
|
void MicroProfileWidget::wheelEvent(QWheelEvent* event) {
|
||||||
MicroProfileMousePosition(event->x() / x_scale, event->y() / y_scale, event->delta() / 120);
|
MicroProfileMousePosition(event->position().x() / x_scale, event->position().y() / y_scale,
|
||||||
|
event->angleDelta().y() / 120);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ void OptionSetDialog::InitializeUI(const std::string& initial_value) {
|
|||||||
ui->formatLabel->text().append(QStringLiteral("\n"));
|
ui->formatLabel->text().append(QStringLiteral("\n"));
|
||||||
}
|
}
|
||||||
ui->formatLabel->setText(
|
ui->formatLabel->setText(
|
||||||
ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min).arg(option.max)));
|
ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min, option.max)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decide and initialize layout
|
// Decide and initialize layout
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
@ -31,6 +32,8 @@
|
|||||||
#include "core/file_sys/archive_extsavedata.h"
|
#include "core/file_sys/archive_extsavedata.h"
|
||||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
#include "qcursor.h"
|
||||||
|
|
||||||
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
|
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
|
||||||
: QObject(parent), gamelist{gamelist} {}
|
: QObject(parent), gamelist{gamelist} {}
|
||||||
@ -169,8 +172,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
|
|||||||
* @return true if the haystack contains all words of userinput
|
* @return true if the haystack contains all words of userinput
|
||||||
*/
|
*/
|
||||||
static bool ContainsAllWords(const QString& haystack, const QString& userinput) {
|
static bool ContainsAllWords(const QString& haystack, const QString& userinput) {
|
||||||
const QStringList userinput_split =
|
const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
||||||
userinput.split(QLatin1Char{' '}, QString::SplitBehavior::SkipEmptyParts);
|
|
||||||
|
|
||||||
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
||||||
[&haystack](const QString& s) { return haystack.contains(s); });
|
[&haystack](const QString& s) { return haystack.contains(s); });
|
||||||
@ -463,6 +465,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,30 +479,38 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
|
|||||||
QAction* open_texture_load_location =
|
QAction* open_texture_load_location =
|
||||||
context_menu.addAction(tr("Open Custom Texture Location"));
|
context_menu.addAction(tr("Open Custom Texture Location"));
|
||||||
QAction* open_mods_location = context_menu.addAction(tr("Open Mods 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* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
|
||||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
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 =
|
const bool is_application =
|
||||||
0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
|
0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
|
||||||
|
|
||||||
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
|
std::string sdmc_dir = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
|
||||||
open_save_location->setVisible(
|
open_save_location->setEnabled(
|
||||||
is_application && FileUtil::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
|
is_application && Common::FS::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
|
||||||
sdmc_dir, program_id)));
|
sdmc_dir, program_id)));
|
||||||
|
|
||||||
if (extdata_id) {
|
if (extdata_id) {
|
||||||
open_extdata_location->setVisible(
|
open_extdata_location->setEnabled(
|
||||||
is_application &&
|
is_application &&
|
||||||
FileUtil::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
|
Common::FS::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
|
||||||
} else {
|
} else {
|
||||||
open_extdata_location->setVisible(false);
|
open_extdata_location->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto media_type = Service::AM::GetTitleMediaType(program_id);
|
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));
|
Service::AM::GetTitleContentPath(media_type, program_id));
|
||||||
open_update_location->setVisible(
|
open_update_location->setEnabled(
|
||||||
is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
|
is_application && Common::FS::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
|
||||||
program_id + 0xe00000000) +
|
program_id + 0xe00000000) +
|
||||||
"content/"));
|
"content/"));
|
||||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||||
@ -511,44 +522,71 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
|
|||||||
|
|
||||||
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
|
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
|
||||||
|
|
||||||
connect(open_save_location, &QAction::triggered, [this, program_id] {
|
connect(open_save_location, &QAction::triggered, this, [this, program_id] {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA);
|
||||||
});
|
});
|
||||||
connect(open_extdata_location, &QAction::triggered, [this, extdata_id] {
|
connect(open_extdata_location, &QAction::triggered, this, [this, extdata_id] {
|
||||||
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
|
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
|
||||||
});
|
});
|
||||||
connect(open_application_location, &QAction::triggered, [this, program_id] {
|
connect(open_application_location, &QAction::triggered, this, [this, program_id] {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
|
||||||
});
|
});
|
||||||
connect(open_update_location, &QAction::triggered, [this, program_id] {
|
connect(open_update_location, &QAction::triggered, this, [this, program_id] {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA);
|
||||||
});
|
});
|
||||||
connect(open_texture_dump_location, &QAction::triggered, [this, program_id] {
|
connect(open_texture_dump_location, &QAction::triggered, this, [this, program_id] {
|
||||||
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
|
Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
|
||||||
program_id))) {
|
program_id))) {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_DUMP);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_DUMP);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(open_texture_load_location, &QAction::triggered, [this, program_id] {
|
connect(open_texture_load_location, &QAction::triggered, this, [this, program_id] {
|
||||||
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
|
||||||
program_id))) {
|
program_id))) {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(open_mods_location, &QAction::triggered, [this, program_id] {
|
connect(open_texture_load_location, &QAction::triggered, this, [this, program_id] {
|
||||||
if (FileUtil::CreateFullPath(fmt::format("{}mods/{:016X}/",
|
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
|
||||||
|
program_id))) {
|
||||||
|
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(open_mods_location, &QAction::triggered, this, [this, program_id] {
|
||||||
|
if (Common::FS::CreateFullPath(fmt::format("{}mods/{:016X}/",
|
||||||
|
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
|
||||||
program_id))) {
|
program_id))) {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(dump_romfs, &QAction::triggered,
|
connect(dump_romfs, &QAction::triggered, this,
|
||||||
[this, path, program_id] { emit DumpRomFSRequested(path, program_id); });
|
[this, path, program_id] { emit DumpRomFSRequested(path, program_id); });
|
||||||
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
|
connect(navigate_to_gamedb_entry, &QAction::triggered, this, [this, program_id]() {
|
||||||
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
|
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) {
|
void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
|
||||||
@ -561,11 +599,11 @@ void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
|
|||||||
deep_scan->setCheckable(true);
|
deep_scan->setCheckable(true);
|
||||||
deep_scan->setChecked(game_dir.deep_scan);
|
deep_scan->setChecked(game_dir.deep_scan);
|
||||||
|
|
||||||
connect(deep_scan, &QAction::triggered, [this, &game_dir] {
|
connect(deep_scan, &QAction::triggered, this, [this, &game_dir] {
|
||||||
game_dir.deep_scan = !game_dir.deep_scan;
|
game_dir.deep_scan = !game_dir.deep_scan;
|
||||||
PopulateAsync(UISettings::values.game_dirs);
|
PopulateAsync(UISettings::values.game_dirs);
|
||||||
});
|
});
|
||||||
connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] {
|
connect(delete_dir, &QAction::triggered, this, [this, &game_dir, selected] {
|
||||||
UISettings::values.game_dirs.removeOne(game_dir);
|
UISettings::values.game_dirs.removeOne(game_dir);
|
||||||
item_model->invisibleRootItem()->removeRow(selected.row());
|
item_model->invisibleRootItem()->removeRow(selected.row());
|
||||||
});
|
});
|
||||||
@ -583,7 +621,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
|
|||||||
move_up->setEnabled(row > 0);
|
move_up->setEnabled(row > 0);
|
||||||
move_down->setEnabled(row < item_model->rowCount() - 2);
|
move_down->setEnabled(row < item_model->rowCount() - 2);
|
||||||
|
|
||||||
connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] {
|
connect(move_up, &QAction::triggered, this, [this, selected, row, game_dir_index] {
|
||||||
const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt();
|
const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt();
|
||||||
// swap the items in the settings
|
// swap the items in the settings
|
||||||
std::swap(UISettings::values.game_dirs[game_dir_index],
|
std::swap(UISettings::values.game_dirs[game_dir_index],
|
||||||
@ -598,7 +636,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
|
|||||||
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
|
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(move_down, &QAction::triggered, [this, selected, row, game_dir_index] {
|
connect(move_down, &QAction::triggered, this, [this, selected, row, game_dir_index] {
|
||||||
const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt();
|
const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt();
|
||||||
// swap the items in the settings
|
// swap the items in the settings
|
||||||
std::swap(UISettings::values.game_dirs[game_dir_index],
|
std::swap(UISettings::values.game_dirs[game_dir_index],
|
||||||
@ -613,7 +651,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
|
|||||||
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
|
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(open_directory_location, &QAction::triggered, [this, game_dir_index] {
|
connect(open_directory_location, &QAction::triggered, this, [this, game_dir_index] {
|
||||||
emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
|
emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -640,7 +678,7 @@ void GameList::LoadCompatibilityList() {
|
|||||||
const QJsonDocument json = QJsonDocument::fromJson(content);
|
const QJsonDocument json = QJsonDocument::fromJson(content);
|
||||||
const QJsonArray arr = json.array();
|
const QJsonArray arr = json.array();
|
||||||
|
|
||||||
for (const QJsonValue value : arr) {
|
for (const QJsonValue& value : arr) {
|
||||||
const QJsonObject game = value.toObject();
|
const QJsonObject game = value.toObject();
|
||||||
const QString compatibility_key = QStringLiteral("compatibility");
|
const QString compatibility_key = QStringLiteral("compatibility");
|
||||||
|
|
||||||
@ -652,7 +690,7 @@ void GameList::LoadCompatibilityList() {
|
|||||||
const QString directory = game[QStringLiteral("directory")].toString();
|
const QString directory = game[QStringLiteral("directory")].toString();
|
||||||
const QJsonArray ids = game[QStringLiteral("releases")].toArray();
|
const QJsonArray ids = game[QStringLiteral("releases")].toArray();
|
||||||
|
|
||||||
for (const QJsonValue id_ref : ids) {
|
for (const QJsonValue& id_ref : ids) {
|
||||||
const QJsonObject id_object = id_ref.toObject();
|
const QJsonObject id_object = id_ref.toObject();
|
||||||
const QString id = id_object[QStringLiteral("id")].toString();
|
const QString id = id_object[QStringLiteral("id")].toString();
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ enum class GameListOpenTarget {
|
|||||||
TEXTURE_DUMP = 4,
|
TEXTURE_DUMP = 4,
|
||||||
TEXTURE_LOAD = 5,
|
TEXTURE_LOAD = 5,
|
||||||
MODS = 6,
|
MODS = 6,
|
||||||
|
SHADER_CACHE = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameList : public QWidget {
|
class GameList : public QWidget {
|
||||||
|
@ -43,7 +43,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string physical_name = directory + DIR_SEP + virtual_name;
|
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)) {
|
if (!is_dir && HasSupportedFileExtension(physical_name)) {
|
||||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
|
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
@ -67,7 +67,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||||||
if (!(program_id & ~0x00040000FFFFFFFF)) {
|
if (!(program_id & ~0x00040000FFFFFFFF)) {
|
||||||
std::string update_path = Service::AM::GetTitleContentPath(
|
std::string update_path = Service::AM::GetTitleContentPath(
|
||||||
Service::FS::MediaType::SDMC, program_id | 0x0000000E00000000);
|
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 =
|
std::unique_ptr<Loader::AppLoader> update_loader =
|
||||||
Loader::GetLoader(update_path);
|
Loader::GetLoader(update_path);
|
||||||
if (update_loader) {
|
if (update_loader) {
|
||||||
@ -101,7 +101,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||||||
new GameListItemRegion(smdh),
|
new GameListItemRegion(smdh),
|
||||||
new GameListItem(
|
new GameListItem(
|
||||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
new GameListItemSize(Common::FS::GetSize(physical_name)),
|
||||||
},
|
},
|
||||||
parent_dir);
|
parent_dir);
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListWorker::run() {
|
void GameListWorker::run() {
|
||||||
@ -121,12 +121,12 @@ void GameListWorker::run() {
|
|||||||
for (UISettings::GameDir& game_dir : game_dirs) {
|
for (UISettings::GameDir& game_dir : game_dirs) {
|
||||||
if (game_dir.path == QStringLiteral("INSTALLED")) {
|
if (game_dir.path == QStringLiteral("INSTALLED")) {
|
||||||
QString games_path =
|
QString games_path =
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) +
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)) +
|
||||||
QStringLiteral("Nintendo "
|
QStringLiteral("Nintendo "
|
||||||
"3DS/00000000000000000000000000000000/"
|
"3DS/00000000000000000000000000000000/"
|
||||||
"00000000000000000000000000000000/title/00040000");
|
"00000000000000000000000000000000/title/00040000");
|
||||||
QString demos_path =
|
QString demos_path =
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) +
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)) +
|
||||||
QStringLiteral(
|
QStringLiteral(
|
||||||
"Nintendo "
|
"Nintendo "
|
||||||
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/"
|
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/"
|
||||||
@ -139,7 +139,7 @@ void GameListWorker::run() {
|
|||||||
AddFstEntriesToGameList(demos_path.toStdString(), 2, game_list_dir);
|
AddFstEntriesToGameList(demos_path.toStdString(), 2, game_list_dir);
|
||||||
} else if (game_dir.path == QStringLiteral("SYSTEM")) {
|
} else if (game_dir.path == QStringLiteral("SYSTEM")) {
|
||||||
QString path =
|
QString path =
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) +
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)) +
|
||||||
QStringLiteral("00000000000000000000000000000000/title/00040010");
|
QStringLiteral("00000000000000000000000000000000/title/00040010");
|
||||||
watch_list.append(path);
|
watch_list.append(path);
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir);
|
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir);
|
||||||
|
@ -11,18 +11,11 @@
|
|||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QOpenGLFunctions_4_3_Core>
|
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#ifdef __APPLE__
|
|
||||||
#include <unistd.h> // for chdir
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
#include "citra_qt/aboutdialog.h"
|
#include "citra_qt/aboutdialog.h"
|
||||||
#include "citra_qt/applets/mii_selector.h"
|
#include "citra_qt/applets/mii_selector.h"
|
||||||
#include "citra_qt/applets/swkbd.h"
|
#include "citra_qt/applets/swkbd.h"
|
||||||
@ -62,12 +55,11 @@
|
|||||||
#include "common/detached_tasks.h"
|
#include "common/detached_tasks.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/filter.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/logging/text_formatter.h"
|
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
#include "common/x64/cpu_detect.h"
|
#include "common/x64/cpu_detect.h"
|
||||||
#endif
|
#endif
|
||||||
@ -76,20 +68,23 @@
|
|||||||
#include "core/file_sys/archive_extsavedata.h"
|
#include "core/file_sys/archive_extsavedata.h"
|
||||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||||
#include "core/frontend/applets/default_applets.h"
|
#include "core/frontend/applets/default_applets.h"
|
||||||
#include "core/frontend/scope_acquire_context.h"
|
|
||||||
#include "core/gdbstub/gdbstub.h"
|
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
#include "core/hle/service/nfc/nfc.h"
|
#include "core/hle/service/nfc/nfc.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/movie.h"
|
#include "core/movie.h"
|
||||||
#include "core/savestate.h"
|
#include "core/savestate.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "game_list_p.h"
|
|
||||||
#include "network/network_settings.h"
|
#include "network/network_settings.h"
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
#include "video_core/renderer_base.h"
|
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <unistd.h> // for chdir
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
#include "citra_qt/discord_impl.h"
|
#include "citra_qt/discord_impl.h"
|
||||||
#endif
|
#endif
|
||||||
@ -143,8 +138,8 @@ static void InitializeLogging() {
|
|||||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||||
Log::SetGlobalFilter(log_filter);
|
Log::SetGlobalFilter(log_filter);
|
||||||
|
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
|
||||||
FileUtil::CreateFullPath(log_dir);
|
Common::FS::CreateFullPath(log_dir);
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||||
@ -152,8 +147,8 @@ static void InitializeLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GMainWindow::GMainWindow()
|
GMainWindow::GMainWindow()
|
||||||
: config(std::make_unique<Config>()), emu_thread(nullptr),
|
: ui{std::make_unique<Ui::MainWindow>()}, config{std::make_unique<Config>()}, emu_thread{
|
||||||
ui(std::make_unique<Ui::MainWindow>()) {
|
nullptr} {
|
||||||
InitializeLogging();
|
InitializeLogging();
|
||||||
Debugger::ToggleConsole();
|
Debugger::ToggleConsole();
|
||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
@ -263,7 +258,7 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
loading_screen = new LoadingScreen(this);
|
loading_screen = new LoadingScreen(this);
|
||||||
loading_screen->hide();
|
loading_screen->hide();
|
||||||
ui->horizontalLayout->addWidget(loading_screen);
|
ui->horizontalLayout->addWidget(loading_screen);
|
||||||
connect(loading_screen, &LoadingScreen::Hidden, [&] {
|
connect(loading_screen, &LoadingScreen::Hidden, this, [&] {
|
||||||
loading_screen->Clear();
|
loading_screen->Clear();
|
||||||
if (emulation_running) {
|
if (emulation_running) {
|
||||||
render_window->show();
|
render_window->show();
|
||||||
@ -282,7 +277,6 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
// Create status bar
|
// Create status bar
|
||||||
message_label = new QLabel();
|
message_label = new QLabel();
|
||||||
// Configured separately for left alignment
|
// Configured separately for left alignment
|
||||||
message_label->setVisible(false);
|
|
||||||
message_label->setFrameStyle(QFrame::NoFrame);
|
message_label->setFrameStyle(QFrame::NoFrame);
|
||||||
message_label->setContentsMargins(4, 0, 4, 0);
|
message_label->setContentsMargins(4, 0, 4, 0);
|
||||||
message_label->setAlignment(Qt::AlignLeft);
|
message_label->setAlignment(Qt::AlignLeft);
|
||||||
@ -307,10 +301,28 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
label->setVisible(false);
|
label->setVisible(false);
|
||||||
label->setFrameStyle(QFrame::NoFrame);
|
label->setFrameStyle(QFrame::NoFrame);
|
||||||
label->setContentsMargins(4, 0, 4, 0);
|
label->setContentsMargins(4, 0, 4, 0);
|
||||||
statusBar()->addPermanentWidget(label, 0);
|
statusBar()->addPermanentWidget(label);
|
||||||
}
|
}
|
||||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
|
|
||||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
|
// Setup Graphics API button
|
||||||
|
graphics_api_button = new QPushButton();
|
||||||
|
graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton"));
|
||||||
|
graphics_api_button->setFocusPolicy(Qt::NoFocus);
|
||||||
|
UpdateAPIIndicator(false);
|
||||||
|
|
||||||
|
connect(graphics_api_button, &QPushButton::clicked, this, [this] {
|
||||||
|
if (emulation_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAPIIndicator(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
statusBar()->insertPermanentWidget(0, graphics_api_button);
|
||||||
|
|
||||||
|
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
|
||||||
|
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
|
||||||
|
|
||||||
statusBar()->setVisible(true);
|
statusBar()->setVisible(true);
|
||||||
|
|
||||||
// Removes an ugly inner border from the status bar widgets under Linux
|
// Removes an ugly inner border from the status bar widgets under Linux
|
||||||
@ -432,13 +444,13 @@ void GMainWindow::InitializeSaveStateMenuActions() {
|
|||||||
ui->menu_Save_State->addAction(actions_save_state[i]);
|
ui->menu_Save_State->addAction(actions_save_state[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, [this] {
|
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, this, [this] {
|
||||||
UpdateSaveStates();
|
UpdateSaveStates();
|
||||||
if (newest_slot != 0) {
|
if (newest_slot != 0) {
|
||||||
actions_load_state[newest_slot - 1]->trigger();
|
actions_load_state[newest_slot - 1]->trigger();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, [this] {
|
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, this, [this] {
|
||||||
UpdateSaveStates();
|
UpdateSaveStates();
|
||||||
actions_save_state[oldest_slot - 1]->trigger();
|
actions_save_state[oldest_slot - 1]->trigger();
|
||||||
});
|
});
|
||||||
@ -674,7 +686,7 @@ void GMainWindow::ConnectWidgetEvents() {
|
|||||||
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
|
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
|
||||||
&GMainWindow::OnGameListAddDirectory);
|
&GMainWindow::OnGameListAddDirectory);
|
||||||
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
|
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
|
||||||
connect(game_list, &GameList::PopulatingCompleted,
|
connect(game_list, &GameList::PopulatingCompleted, this,
|
||||||
[this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
|
[this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
|
||||||
|
|
||||||
connect(this, &GMainWindow::EmulationStarting, render_window,
|
connect(this, &GMainWindow::EmulationStarting, render_window,
|
||||||
@ -765,7 +777,7 @@ void GMainWindow::ConnectMenuEvents() {
|
|||||||
connect(ui->action_Close_Movie, &QAction::triggered, this, &GMainWindow::OnCloseMovie);
|
connect(ui->action_Close_Movie, &QAction::triggered, this, &GMainWindow::OnCloseMovie);
|
||||||
connect(ui->action_Save_Movie, &QAction::triggered, this, &GMainWindow::OnSaveMovie);
|
connect(ui->action_Save_Movie, &QAction::triggered, this, &GMainWindow::OnSaveMovie);
|
||||||
connect(ui->action_Movie_Read_Only_Mode, &QAction::toggled, this,
|
connect(ui->action_Movie_Read_Only_Mode, &QAction::toggled, this,
|
||||||
[this](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); });
|
[](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); });
|
||||||
connect(ui->action_Enable_Frame_Advancing, &QAction::triggered, this, [this] {
|
connect(ui->action_Enable_Frame_Advancing, &QAction::triggered, this, [this] {
|
||||||
if (emulation_running) {
|
if (emulation_running) {
|
||||||
Core::System::GetInstance().frame_limiter.SetFrameAdvancing(
|
Core::System::GetInstance().frame_limiter.SetFrameAdvancing(
|
||||||
@ -914,20 +926,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
|||||||
|
|
||||||
render_window->InitRenderTarget();
|
render_window->InitRenderTarget();
|
||||||
|
|
||||||
Frontend::ScopeAcquireContext scope(*render_window);
|
const auto scope = render_window->Acquire();
|
||||||
|
|
||||||
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
|
Core::System& system = Core::System::GetInstance();
|
||||||
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
|
const Core::System::ResultStatus result = system.Load(*render_window, filename.toStdString());
|
||||||
"have the latest graphics driver.");
|
|
||||||
|
|
||||||
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
|
|
||||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::System& system{Core::System::GetInstance()};
|
|
||||||
|
|
||||||
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
|
|
||||||
|
|
||||||
if (result != Core::System::ResultStatus::Success) {
|
if (result != Core::System::ResultStatus::Success) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
@ -976,7 +978,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
|||||||
case Core::System::ResultStatus::ErrorVideoCore:
|
case Core::System::ResultStatus::ErrorVideoCore:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Video Core Error"),
|
this, tr("Video Core Error"),
|
||||||
tr("An error has occurred. Please <a "
|
tr("An error has occurred during intialization of the video backend. Please <a "
|
||||||
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
|
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
|
||||||
"the "
|
"the "
|
||||||
"log</a> for more details. "
|
"log</a> for more details. "
|
||||||
@ -991,10 +993,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
|||||||
"proper drivers for your graphics card from the manufacturer's website."));
|
"proper drivers for your graphics card from the manufacturer's website."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
|
|
||||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Error while loading ROM!"),
|
this, tr("Error while loading ROM!"),
|
||||||
@ -1206,7 +1204,6 @@ void GMainWindow::ShutdownGame() {
|
|||||||
|
|
||||||
// Disable status bar updates
|
// Disable status bar updates
|
||||||
status_bar_update_timer.stop();
|
status_bar_update_timer.stop();
|
||||||
message_label->setVisible(false);
|
|
||||||
message_label_used_for_movie = false;
|
message_label_used_for_movie = false;
|
||||||
emu_speed_label->setVisible(false);
|
emu_speed_label->setVisible(false);
|
||||||
game_fps_label->setVisible(false);
|
game_fps_label->setVisible(false);
|
||||||
@ -1327,13 +1324,13 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
|
|||||||
switch (target) {
|
switch (target) {
|
||||||
case GameListOpenTarget::SAVE_DATA: {
|
case GameListOpenTarget::SAVE_DATA: {
|
||||||
open_target = "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);
|
path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, data_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GameListOpenTarget::EXT_DATA: {
|
case GameListOpenTarget::EXT_DATA: {
|
||||||
open_target = "Extra 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);
|
path = FileSys::GetExtDataPathFromId(sdmc_dir, data_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1343,26 +1340,35 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
|
|||||||
path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
|
path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GameListOpenTarget::UPDATE_DATA:
|
case GameListOpenTarget::UPDATE_DATA: {
|
||||||
open_target = "Update Data";
|
open_target = "Update Data";
|
||||||
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
|
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
|
||||||
"content/";
|
"content/";
|
||||||
break;
|
break;
|
||||||
case GameListOpenTarget::TEXTURE_DUMP:
|
}
|
||||||
|
case GameListOpenTarget::TEXTURE_DUMP: {
|
||||||
open_target = "Dumped Textures";
|
open_target = "Dumped Textures";
|
||||||
path = fmt::format("{}textures/{:016X}/",
|
path = fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), data_id);
|
Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), data_id);
|
||||||
break;
|
break;
|
||||||
case GameListOpenTarget::TEXTURE_LOAD:
|
}
|
||||||
|
case GameListOpenTarget::TEXTURE_LOAD: {
|
||||||
open_target = "Custom Textures";
|
open_target = "Custom Textures";
|
||||||
path = fmt::format("{}textures/{:016X}/",
|
path = fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id);
|
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), data_id);
|
||||||
break;
|
break;
|
||||||
case GameListOpenTarget::MODS:
|
}
|
||||||
|
case GameListOpenTarget::MODS: {
|
||||||
open_target = "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);
|
data_id);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case GameListOpenTarget::SHADER_CACHE: {
|
||||||
|
open_target = "Shader Cache";
|
||||||
|
path = Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
|
LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
|
||||||
return;
|
return;
|
||||||
@ -1404,13 +1410,13 @@ void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
|
|||||||
dialog->setValue(0);
|
dialog->setValue(0);
|
||||||
|
|
||||||
const auto base_path = fmt::format(
|
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 =
|
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);
|
program_id | 0x0004000e00000000);
|
||||||
using FutureWatcher = QFutureWatcher<std::pair<Loader::ResultStatus, Loader::ResultStatus>>;
|
using FutureWatcher = QFutureWatcher<std::pair<Loader::ResultStatus, Loader::ResultStatus>>;
|
||||||
auto* future_watcher = new FutureWatcher(this);
|
auto* future_watcher = new FutureWatcher(this);
|
||||||
connect(future_watcher, &FutureWatcher::finished,
|
connect(future_watcher, &FutureWatcher::finished, this,
|
||||||
[this, dialog, base_path, update_path, future_watcher] {
|
[this, dialog, base_path, update_path, future_watcher] {
|
||||||
dialog->hide();
|
dialog->hide();
|
||||||
const auto& [base, update] = future_watcher->result();
|
const auto& [base, update] = future_watcher->result();
|
||||||
@ -1437,12 +1443,12 @@ void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
|
|||||||
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
|
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
|
||||||
QString path;
|
QString path;
|
||||||
if (directory == QStringLiteral("INSTALLED")) {
|
if (directory == QStringLiteral("INSTALLED")) {
|
||||||
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
|
||||||
"Nintendo "
|
"Nintendo "
|
||||||
"3DS/00000000000000000000000000000000/"
|
"3DS/00000000000000000000000000000000/"
|
||||||
"00000000000000000000000000000000/title/00040000");
|
"00000000000000000000000000000000/title/00040000");
|
||||||
} else if (directory == QStringLiteral("SYSTEM")) {
|
} 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");
|
"00000000000000000000000000000000/title/00040010");
|
||||||
} else {
|
} else {
|
||||||
path = directory;
|
path = directory;
|
||||||
@ -1512,7 +1518,7 @@ void GMainWindow::InstallCIA(QStringList filepaths) {
|
|||||||
const auto cia_progress = [&](std::size_t written, std::size_t total) {
|
const auto cia_progress = [&](std::size_t written, std::size_t total) {
|
||||||
emit UpdateProgress(written, total);
|
emit UpdateProgress(written, total);
|
||||||
};
|
};
|
||||||
for (const auto current_path : filepaths) {
|
for (const auto& current_path : filepaths) {
|
||||||
status = Service::AM::InstallCIA(current_path.toStdString(), cia_progress);
|
status = Service::AM::InstallCIA(current_path.toStdString(), cia_progress);
|
||||||
emit CIAInstallReport(status, current_path);
|
emit CIAInstallReport(status, current_path);
|
||||||
}
|
}
|
||||||
@ -1550,6 +1556,10 @@ void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString
|
|||||||
"before being used with Citra. A real 3DS is required.")
|
"before being used with Citra. A real 3DS is required.")
|
||||||
.arg(filename));
|
.arg(filename));
|
||||||
break;
|
break;
|
||||||
|
case Service::AM::InstallStatus::ErrorFileNotFound:
|
||||||
|
QMessageBox::critical(this, tr("Unable to find File"),
|
||||||
|
tr("Could not find %1").arg(filename));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1727,6 +1737,8 @@ void GMainWindow::ToggleScreenLayout() {
|
|||||||
case Settings::LayoutOption::SideScreen:
|
case Settings::LayoutOption::SideScreen:
|
||||||
new_layout = Settings::LayoutOption::Default;
|
new_layout = Settings::LayoutOption::Default;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::values.layout_option = new_layout;
|
Settings::values.layout_option = new_layout;
|
||||||
@ -1796,6 +1808,7 @@ void GMainWindow::OnConfigure() {
|
|||||||
} else {
|
} else {
|
||||||
setMouseTracking(false);
|
setMouseTracking(false);
|
||||||
}
|
}
|
||||||
|
UpdateAPIIndicator(false);
|
||||||
} else {
|
} else {
|
||||||
Settings::values.input_profiles = old_input_profiles;
|
Settings::values.input_profiles = old_input_profiles;
|
||||||
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
|
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
|
||||||
@ -1860,7 +1873,7 @@ void GMainWindow::OnRemoveAmiibo() {
|
|||||||
|
|
||||||
void GMainWindow::OnOpenCitraFolder() {
|
void GMainWindow::OnOpenCitraFolder() {
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
||||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnToggleFilterBar() {
|
void GMainWindow::OnToggleFilterBar() {
|
||||||
@ -1960,12 +1973,12 @@ void GMainWindow::OnSaveMovie() {
|
|||||||
void GMainWindow::OnCaptureScreenshot() {
|
void GMainWindow::OnCaptureScreenshot() {
|
||||||
OnPauseGame();
|
OnPauseGame();
|
||||||
QString path = UISettings::values.screenshot_path;
|
QString path = UISettings::values.screenshot_path;
|
||||||
if (!FileUtil::IsDirectory(path.toStdString())) {
|
if (!Common::FS::IsDirectory(path.toStdString())) {
|
||||||
if (!FileUtil::CreateFullPath(path.toStdString())) {
|
if (!Common::FS::CreateFullPath(path.toStdString())) {
|
||||||
QMessageBox::information(this, tr("Invalid Screenshot Directory"),
|
QMessageBox::information(this, tr("Invalid Screenshot Directory"),
|
||||||
tr("Cannot create specified screenshot directory. Screenshot "
|
tr("Cannot create specified screenshot directory. Screenshot "
|
||||||
"path is set back to its default value."));
|
"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/"));
|
path.append(QStringLiteral("screenshots/"));
|
||||||
UISettings::values.screenshot_path = path;
|
UISettings::values.screenshot_path = path;
|
||||||
};
|
};
|
||||||
@ -1973,7 +1986,7 @@ void GMainWindow::OnCaptureScreenshot() {
|
|||||||
const QString filename = game_title.remove(QRegularExpression(QStringLiteral("[\\/:?\"<>|]")));
|
const QString filename = game_title.remove(QRegularExpression(QStringLiteral("[\\/:?\"<>|]")));
|
||||||
const QString timestamp =
|
const QString timestamp =
|
||||||
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
|
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
|
||||||
path.append(QStringLiteral("/%1_%2.png").arg(filename).arg(timestamp));
|
path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
|
||||||
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
|
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
|
||||||
OnStartGame();
|
OnStartGame();
|
||||||
}
|
}
|
||||||
@ -2048,7 +2061,7 @@ void GMainWindow::UpdateStatusBar() {
|
|||||||
message_label_used_for_movie = true;
|
message_label_used_for_movie = true;
|
||||||
ui->action_Save_Movie->setEnabled(true);
|
ui->action_Save_Movie->setEnabled(true);
|
||||||
} else if (play_mode == Core::Movie::PlayMode::Playing) {
|
} else if (play_mode == Core::Movie::PlayMode::Playing) {
|
||||||
message_label->setText(tr("Playing %1 / %2").arg(current).arg(total));
|
message_label->setText(tr("Playing %1 / %2").arg(current, total));
|
||||||
message_label->setVisible(true);
|
message_label->setVisible(true);
|
||||||
message_label_used_for_movie = true;
|
message_label_used_for_movie = true;
|
||||||
ui->action_Save_Movie->setEnabled(false);
|
ui->action_Save_Movie->setEnabled(false);
|
||||||
@ -2107,6 +2120,26 @@ void GMainWindow::ShowMouseCursor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::UpdateAPIIndicator(bool override) {
|
||||||
|
static std::array graphics_apis = {QStringLiteral("OPENGL"), QStringLiteral("OPENGLES"),
|
||||||
|
QStringLiteral("VULKAN")};
|
||||||
|
|
||||||
|
static std::array graphics_api_colors = {QStringLiteral("#00ccdd"), QStringLiteral("#ba2a8d"),
|
||||||
|
QStringLiteral("#91242a")};
|
||||||
|
|
||||||
|
u32 api_index = static_cast<u32>(Settings::values.graphics_api);
|
||||||
|
if (override) {
|
||||||
|
api_index = (api_index + 1) % graphics_apis.size();
|
||||||
|
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString style_sheet = QStringLiteral("QPushButton { font-weight: bold; color: %0; }")
|
||||||
|
.arg(graphics_api_colors[api_index]);
|
||||||
|
|
||||||
|
graphics_api_button->setText(graphics_apis[api_index]);
|
||||||
|
graphics_api_button->setStyleSheet(style_sheet);
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMouseActivity() {
|
void GMainWindow::OnMouseActivity() {
|
||||||
ShowMouseCursor();
|
ShowMouseCursor();
|
||||||
}
|
}
|
||||||
@ -2292,8 +2325,16 @@ void GMainWindow::UpdateUITheme() {
|
|||||||
QStringList theme_paths(default_theme_paths);
|
QStringList theme_paths(default_theme_paths);
|
||||||
|
|
||||||
if (is_default_theme || current_theme.isEmpty()) {
|
if (is_default_theme || current_theme.isEmpty()) {
|
||||||
qApp->setStyleSheet({});
|
const QString theme_uri(QStringLiteral(":default/style.qss"));
|
||||||
setStyleSheet({});
|
QFile f(theme_uri);
|
||||||
|
if (f.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
QTextStream ts(&f);
|
||||||
|
qApp->setStyleSheet(ts.readAll());
|
||||||
|
setStyleSheet(ts.readAll());
|
||||||
|
} else {
|
||||||
|
qApp->setStyleSheet({});
|
||||||
|
setStyleSheet({});
|
||||||
|
}
|
||||||
theme_paths.append(default_icons);
|
theme_paths.append(default_icons);
|
||||||
QIcon::setThemeName(default_icons);
|
QIcon::setThemeName(default_icons);
|
||||||
} else {
|
} else {
|
||||||
@ -2440,16 +2481,8 @@ int main(int argc, char* argv[]) {
|
|||||||
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||||
|
|
||||||
QSurfaceFormat format;
|
|
||||||
format.setVersion(4, 3);
|
|
||||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
|
||||||
format.setSwapInterval(0);
|
|
||||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
|
||||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + "..";
|
||||||
chdir(bin_path.c_str());
|
chdir(bin_path.c_str());
|
||||||
#endif
|
#endif
|
||||||
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include "citra_qt/compatibility_list.h"
|
#include "citra_qt/compatibility_list.h"
|
||||||
@ -234,6 +235,7 @@ private:
|
|||||||
void InstallCIA(QStringList filepaths);
|
void InstallCIA(QStringList filepaths);
|
||||||
void HideMouseCursor();
|
void HideMouseCursor();
|
||||||
void ShowMouseCursor();
|
void ShowMouseCursor();
|
||||||
|
void UpdateAPIIndicator(bool override);
|
||||||
|
|
||||||
std::unique_ptr<Ui::MainWindow> ui;
|
std::unique_ptr<Ui::MainWindow> ui;
|
||||||
|
|
||||||
@ -248,6 +250,7 @@ private:
|
|||||||
QLabel* emu_speed_label = nullptr;
|
QLabel* emu_speed_label = nullptr;
|
||||||
QLabel* game_fps_label = nullptr;
|
QLabel* game_fps_label = nullptr;
|
||||||
QLabel* emu_frametime_label = nullptr;
|
QLabel* emu_frametime_label = nullptr;
|
||||||
|
QPushButton* graphics_api_button = nullptr;
|
||||||
QTimer status_bar_update_timer;
|
QTimer status_bar_update_timer;
|
||||||
bool message_label_used_for_movie = false;
|
bool message_label_used_for_movie = false;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include "citra_qt/game_list.h"
|
|
||||||
#include "citra_qt/multiplayer/client_room.h"
|
#include "citra_qt/multiplayer/client_room.h"
|
||||||
#include "citra_qt/multiplayer/direct_connect.h"
|
#include "citra_qt/multiplayer/direct_connect.h"
|
||||||
#include "citra_qt/multiplayer/host_room.h"
|
#include "citra_qt/multiplayer/host_room.h"
|
||||||
@ -16,7 +15,6 @@
|
|||||||
#include "citra_qt/multiplayer/state.h"
|
#include "citra_qt/multiplayer/state.h"
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
#include "citra_qt/util/clickable_label.h"
|
#include "citra_qt/util/clickable_label.h"
|
||||||
#include "common/announce_multiplayer_room.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model,
|
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model,
|
||||||
@ -231,8 +229,9 @@ bool MultiplayerState::OnCloseRoom() {
|
|||||||
if (room->GetState() != Network::Room::State::Open) {
|
if (room->GetState() != Network::Room::State::Open) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save ban list
|
// Save ban list
|
||||||
UISettings::values.ban_list = std::move(room->GetBanList());
|
UISettings::values.ban_list = room->GetBanList();
|
||||||
|
|
||||||
room->Destroy();
|
room->Destroy();
|
||||||
announce_multiplayer_session->Stop();
|
announce_multiplayer_session->Stop();
|
||||||
|
@ -69,15 +69,27 @@ add_library(common STATIC
|
|||||||
common_funcs.h
|
common_funcs.h
|
||||||
common_paths.h
|
common_paths.h
|
||||||
common_types.h
|
common_types.h
|
||||||
|
concepts.h
|
||||||
construct.h
|
construct.h
|
||||||
file_util.cpp
|
#file_util.cpp
|
||||||
file_util.h
|
#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
|
hash.h
|
||||||
linear_disk_cache.h
|
linear_disk_cache.h
|
||||||
logging/backend.cpp
|
logging/backend.cpp
|
||||||
logging/backend.h
|
logging/backend.h
|
||||||
logging/filter.cpp
|
logging/filter.cpp
|
||||||
logging/filter.h
|
logging/filter.h
|
||||||
|
logging/formatter.h
|
||||||
logging/log.h
|
logging/log.h
|
||||||
logging/text_formatter.cpp
|
logging/text_formatter.cpp
|
||||||
logging/text_formatter.h
|
logging/text_formatter.h
|
||||||
|
@ -59,6 +59,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
|||||||
|
|
||||||
#endif // _MSC_VER ndef
|
#endif // _MSC_VER ndef
|
||||||
|
|
||||||
|
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
||||||
|
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
|
||||||
|
} \
|
||||||
|
constexpr type& operator|=(type& a, type b) noexcept { \
|
||||||
|
a = a | b; \
|
||||||
|
return a; \
|
||||||
|
} \
|
||||||
|
constexpr type& operator&=(type& a, type b) noexcept { \
|
||||||
|
a = a & b; \
|
||||||
|
return a; \
|
||||||
|
} \
|
||||||
|
constexpr type& operator^=(type& a, type b) noexcept { \
|
||||||
|
a = a ^ b; \
|
||||||
|
return a; \
|
||||||
|
} \
|
||||||
|
constexpr type& operator<<=(type& a, type b) noexcept { \
|
||||||
|
a = a << b; \
|
||||||
|
return a; \
|
||||||
|
} \
|
||||||
|
constexpr type& operator>>=(type& a, type b) noexcept { \
|
||||||
|
a = a >> b; \
|
||||||
|
return a; \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr type operator~(type key) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<type>(~static_cast<T>(key)); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr bool True(type key) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<T>(key) != 0; \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] constexpr bool False(type key) noexcept { \
|
||||||
|
using T = std::underlying_type_t<type>; \
|
||||||
|
return static_cast<T>(key) == 0; \
|
||||||
|
}
|
||||||
|
|
||||||
// Generic function to get last error message.
|
// Generic function to get last error message.
|
||||||
// Call directly after the command or use the error num.
|
// Call directly after the command or use the error num.
|
||||||
// This function might change the error code.
|
// This function might change the error code.
|
||||||
|
35
src/common/concepts.h
Normal file
35
src/common/concepts.h
Normal 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
@ -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
415
src/common/fs/file.cpp
Normal 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
459
src/common/fs/file.h
Normal 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
624
src/common/fs/fs.cpp
Normal 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
583
src/common/fs/fs.h
Normal 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
25
src/common/fs/fs_paths.h
Normal 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"
|
6
src/common/fs/fs_serialize.cpp
Normal file
6
src/common/fs/fs_serialize.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include "fs_serialize.h"
|
||||||
|
|
||||||
|
fs_serialize::fs_serialize()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
43
src/common/fs/fs_serialize.h
Normal file
43
src/common/fs/fs_serialize.h
Normal 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
71
src/common/fs/fs_types.h
Normal 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
38
src/common/fs/fs_util.cpp
Normal 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
85
src/common/fs/fs_util.h
Normal 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
428
src/common/fs/path_util.cpp
Normal 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
302
src/common/fs/path_util.h
Normal 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
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "common/cityhash.h"
|
#include "common/cityhash.h"
|
||||||
@ -41,6 +42,13 @@ inline u64 HashCombine(std::size_t& seed, const u64 hash) {
|
|||||||
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IdentityHash {
|
||||||
|
T operator()(const T& value) const {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
|
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct HashableStruct {
|
struct HashableStruct {
|
||||||
|
@ -12,12 +12,10 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <share.h> // For _SH_DENYWR
|
|
||||||
#include <windows.h> // For OutputDebugStringW
|
#include <windows.h> // For OutputDebugStringW
|
||||||
#else
|
|
||||||
#define _SH_DENYWR 0
|
|
||||||
#endif
|
#endif
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/fs/fs.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/logging/text_formatter.h"
|
#include "common/logging/text_formatter.h"
|
||||||
@ -144,17 +142,16 @@ void LogcatBackend::Write(const Entry& entry) {
|
|||||||
PrintMessageToLogcat(entry);
|
PrintMessageToLogcat(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileBackend::FileBackend(const std::string& filename) : bytes_written(0) {
|
FileBackend::FileBackend(const std::filesystem::path& filename) : bytes_written(0) {
|
||||||
if (FileUtil::Exists(filename + ".old.txt")) {
|
auto old_filename = filename;
|
||||||
FileUtil::Delete(filename + ".old.txt");
|
old_filename += ".old.txt";
|
||||||
}
|
|
||||||
if (FileUtil::Exists(filename)) {
|
|
||||||
FileUtil::Rename(filename, filename + ".old.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
// _SH_DENYWR allows read only access to the file for other programs.
|
// Existence checks are done within the functions themselves.
|
||||||
// It is #defined to 0 on other platforms
|
// We don't particularly care if these succeed or not.
|
||||||
file = FileUtil::IOFile(filename, "w", _SH_DENYWR);
|
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) {
|
void FileBackend::Write(const Entry& entry) {
|
||||||
@ -235,6 +232,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
|||||||
CLS(Render) \
|
CLS(Render) \
|
||||||
SUB(Render, Software) \
|
SUB(Render, Software) \
|
||||||
SUB(Render, OpenGL) \
|
SUB(Render, OpenGL) \
|
||||||
|
SUB(Render, Vulkan) \
|
||||||
CLS(Audio) \
|
CLS(Audio) \
|
||||||
SUB(Audio, DSP) \
|
SUB(Audio, DSP) \
|
||||||
SUB(Audio, Sink) \
|
SUB(Audio, Sink) \
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include "common/file_util.h"
|
#include "common/fs/file.h"
|
||||||
#include "common/logging/filter.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
namespace Log {
|
namespace Log {
|
||||||
@ -101,7 +101,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
class FileBackend : public Backend {
|
class FileBackend : public Backend {
|
||||||
public:
|
public:
|
||||||
explicit FileBackend(const std::string& filename);
|
explicit FileBackend(const std::filesystem::path& filename);
|
||||||
|
|
||||||
static const char* Name() {
|
static const char* Name() {
|
||||||
return "file";
|
return "file";
|
||||||
@ -114,7 +114,7 @@ public:
|
|||||||
void Write(const Entry& entry) override;
|
void Write(const Entry& entry) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileUtil::IOFile file;
|
Common::FS::IOFile file;
|
||||||
std::size_t bytes_written;
|
std::size_t bytes_written;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/formatter.h"
|
#include "common/logging/formatter.h"
|
||||||
|
|
||||||
namespace Log {
|
namespace Log {
|
||||||
|
|
||||||
// trims up to and including the last of ../, ..\, src/, src\ in a string
|
// trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||||
@ -102,6 +103,7 @@ enum class Class : ClassType {
|
|||||||
Render, ///< Emulator video output and hardware acceleration
|
Render, ///< Emulator video output and hardware acceleration
|
||||||
Render_Software, ///< Software renderer backend
|
Render_Software, ///< Software renderer backend
|
||||||
Render_OpenGL, ///< OpenGL backend
|
Render_OpenGL, ///< OpenGL backend
|
||||||
|
Render_Vulkan, ///< Vulkan backend
|
||||||
Audio, ///< Audio emulation
|
Audio, ///< Audio emulation
|
||||||
Audio_DSP, ///< The HLE and LLE implementations of the DSP
|
Audio_DSP, ///< The HLE and LLE implementations of the DSP
|
||||||
Audio_Sink, ///< Emulator audio output backend
|
Audio_Sink, ///< Emulator audio output backend
|
||||||
|
@ -4,13 +4,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <compare>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
constexpr float PI = 3.14159265f;
|
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct Rectangle {
|
struct Rectangle {
|
||||||
T left{};
|
T left{};
|
||||||
@ -23,19 +22,31 @@ struct Rectangle {
|
|||||||
constexpr Rectangle(T left, T top, T right, T bottom)
|
constexpr Rectangle(T left, T top, T right, T bottom)
|
||||||
: left(left), top(top), right(right), bottom(bottom) {}
|
: left(left), top(top), right(right), bottom(bottom) {}
|
||||||
|
|
||||||
[[nodiscard]] T GetWidth() const {
|
constexpr auto operator<=>(const Rectangle&) const = default;
|
||||||
|
|
||||||
|
constexpr void operator*=(const T value) {
|
||||||
|
left *= value;
|
||||||
|
top *= value;
|
||||||
|
right *= value;
|
||||||
|
bottom *= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Rectangle operator*(const T value) const {
|
||||||
|
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||||
|
}
|
||||||
|
[[nodiscard]] constexpr T GetWidth() const {
|
||||||
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
||||||
}
|
}
|
||||||
[[nodiscard]] T GetHeight() const {
|
[[nodiscard]] constexpr T GetHeight() const {
|
||||||
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
||||||
}
|
}
|
||||||
[[nodiscard]] Rectangle<T> TranslateX(const T x) const {
|
[[nodiscard]] constexpr Rectangle<T> TranslateX(const T x) const {
|
||||||
return Rectangle{left + x, top, right + x, bottom};
|
return Rectangle{left + x, top, right + x, bottom};
|
||||||
}
|
}
|
||||||
[[nodiscard]] Rectangle<T> TranslateY(const T y) const {
|
[[nodiscard]] constexpr Rectangle<T> TranslateY(const T y) const {
|
||||||
return Rectangle{left, top + y, right, bottom + y};
|
return Rectangle{left, top + y, right, bottom + y};
|
||||||
}
|
}
|
||||||
[[nodiscard]] Rectangle<T> Scale(const float s) const {
|
[[nodiscard]] constexpr Rectangle<T> Scale(const float s) const {
|
||||||
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
|
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
|
||||||
static_cast<T>(top + GetHeight() * s)};
|
static_cast<T>(top + GetHeight() * s)};
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/serialization/export.hpp>
|
#include <boost/serialization/export.hpp>
|
||||||
#include <boost/serialization/shared_ptr.hpp>
|
#include <boost/serialization/shared_ptr.hpp>
|
||||||
@ -65,8 +65,11 @@ private:
|
|||||||
|
|
||||||
BOOST_CLASS_EXPORT_KEY(BufferMem);
|
BOOST_CLASS_EXPORT_KEY(BufferMem);
|
||||||
|
|
||||||
/// A managed reference to host-side memory. Fast enough to be used everywhere instead of u8*
|
/**
|
||||||
/// Supports serialization.
|
* A managed reference to host-side memory.
|
||||||
|
* Fast enough to be used everywhere instead of u8*
|
||||||
|
* Supports serialization.
|
||||||
|
*/
|
||||||
class MemoryRef {
|
class MemoryRef {
|
||||||
public:
|
public:
|
||||||
MemoryRef() = default;
|
MemoryRef() = default;
|
||||||
@ -75,35 +78,52 @@ public:
|
|||||||
: backing_mem(std::move(backing_mem_)), offset(0) {
|
: backing_mem(std::move(backing_mem_)), offset(0) {
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
|
MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
|
||||||
: backing_mem(std::move(backing_mem_)), offset(offset_) {
|
: backing_mem(std::move(backing_mem_)), offset(offset_) {
|
||||||
ASSERT(offset <= backing_mem->GetSize());
|
ASSERT(offset <= backing_mem->GetSize());
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return cptr != nullptr;
|
return cptr != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
operator u8*() {
|
operator u8*() {
|
||||||
return cptr;
|
return cptr;
|
||||||
}
|
}
|
||||||
u8* GetPtr() {
|
|
||||||
return cptr;
|
|
||||||
}
|
|
||||||
operator const u8*() const {
|
operator const u8*() const {
|
||||||
return cptr;
|
return cptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u8* GetPtr() {
|
||||||
|
return cptr;
|
||||||
|
}
|
||||||
|
|
||||||
const u8* GetPtr() const {
|
const u8* GetPtr() const {
|
||||||
return cptr;
|
return cptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GetWriteBytes(std::size_t size) {
|
||||||
|
return std::span{reinterpret_cast<std::byte*>(cptr), size > csize ? csize : size};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetReadBytes(std::size_t size) const {
|
||||||
|
return std::span{reinterpret_cast<const std::byte*>(cptr), size > csize ? csize : size};
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t GetSize() const {
|
std::size_t GetSize() const {
|
||||||
return csize;
|
return csize;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryRef& operator+=(u32 offset_by) {
|
MemoryRef& operator+=(u32 offset_by) {
|
||||||
ASSERT(offset_by < csize);
|
ASSERT(offset_by < csize);
|
||||||
offset += offset_by;
|
offset += offset_by;
|
||||||
Init();
|
Init();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryRef operator+(u32 offset_by) const {
|
MemoryRef operator+(u32 offset_by) const {
|
||||||
ASSERT(offset_by < csize);
|
ASSERT(offset_by < csize);
|
||||||
return MemoryRef(backing_mem, offset + offset_by);
|
return MemoryRef(backing_mem, offset + offset_by);
|
||||||
|
@ -23,12 +23,3 @@ typedef void* HANDLE;
|
|||||||
#include <microprofile.h>
|
#include <microprofile.h>
|
||||||
|
|
||||||
#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0)
|
#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0)
|
||||||
|
|
||||||
// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with
|
|
||||||
// identifiers we use.
|
|
||||||
#ifdef PAGE_SIZE
|
|
||||||
#undef PAGE_SIZE
|
|
||||||
#endif
|
|
||||||
#ifdef PAGE_MASK
|
|
||||||
#undef PAGE_MASK
|
|
||||||
#endif
|
|
||||||
|
@ -110,8 +110,6 @@ add_library(core STATIC
|
|||||||
frontend/input.h
|
frontend/input.h
|
||||||
frontend/mic.cpp
|
frontend/mic.cpp
|
||||||
frontend/mic.h
|
frontend/mic.h
|
||||||
frontend/scope_acquire_context.cpp
|
|
||||||
frontend/scope_acquire_context.h
|
|
||||||
gdbstub/gdbstub.cpp
|
gdbstub/gdbstub.cpp
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
hle/applets/applet.cpp
|
hle/applets/applet.cpp
|
||||||
|
@ -261,6 +261,8 @@ u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) const {
|
|||||||
default:
|
default:
|
||||||
UNREACHABLE_MSG("Unknown CP15 register: {}", reg);
|
UNREACHABLE_MSG("Unknown CP15 register: {}", reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
|
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
|
||||||
|
@ -849,17 +849,13 @@ static int InterpreterTranslateBlock(ARMul_State* cpu, std::size_t& bb_start, u3
|
|||||||
// Save start addr of basicblock in CreamCache
|
// Save start addr of basicblock in CreamCache
|
||||||
ARM_INST_PTR inst_base = nullptr;
|
ARM_INST_PTR inst_base = nullptr;
|
||||||
TransExtData ret = TransExtData::NON_BRANCH;
|
TransExtData ret = TransExtData::NON_BRANCH;
|
||||||
int size = 0; // instruction size of basic block
|
|
||||||
bb_start = trans_cache_buf_top;
|
bb_start = trans_cache_buf_top;
|
||||||
|
|
||||||
u32 phys_addr = addr;
|
u32 phys_addr = addr;
|
||||||
u32 pc_start = cpu->Reg[15];
|
u32 pc_start = cpu->Reg[15];
|
||||||
|
|
||||||
while (ret == TransExtData::NON_BRANCH) {
|
while (ret == TransExtData::NON_BRANCH) {
|
||||||
unsigned int inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base);
|
u32 inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base);
|
||||||
|
|
||||||
size++;
|
|
||||||
|
|
||||||
phys_addr += inst_size;
|
phys_addr += inst_size;
|
||||||
|
|
||||||
if ((phys_addr & 0xfff) == 0) {
|
if ((phys_addr & 0xfff) == 0) {
|
||||||
@ -972,7 +968,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
|||||||
|
|
||||||
// GCC and Clang have a C++ extension to support a lookup table of labels. Otherwise, fallback to a
|
// GCC and Clang have a C++ extension to support a lookup table of labels. Otherwise, fallback to a
|
||||||
// clunky switch statement.
|
// clunky switch statement.
|
||||||
#if defined __GNUC__ || defined __clang__
|
#if defined __GNUC__ || (defined __clang__ && !defined _MSC_VER)
|
||||||
#define GOTO_NEXT_INST \
|
#define GOTO_NEXT_INST \
|
||||||
GDB_BP_CHECK; \
|
GDB_BP_CHECK; \
|
||||||
if (num_instrs >= cpu->NumInstrsToExecute) \
|
if (num_instrs >= cpu->NumInstrsToExecute) \
|
||||||
|
@ -1218,7 +1218,7 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {
|
|||||||
|
|
||||||
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
|
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
|
||||||
u32 except;
|
u32 except;
|
||||||
char type;
|
[[maybe_unused]] char type;
|
||||||
|
|
||||||
type = (fop->flags & OP_SD) ? 's' : 'd';
|
type = (fop->flags & OP_SD) ? 's' : 'd';
|
||||||
if (op == FOP_EXT)
|
if (op == FOP_EXT)
|
||||||
|
@ -1242,7 +1242,7 @@ u32 vfp_single_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {
|
|||||||
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
|
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
|
||||||
s32 m = vfp_get_float(state, sm);
|
s32 m = vfp_get_float(state, sm);
|
||||||
u32 except;
|
u32 except;
|
||||||
char type;
|
[[maybe_unused]] char type;
|
||||||
|
|
||||||
type = (fop->flags & OP_DD) ? 'd' : 's';
|
type = (fop->flags & OP_DD) ? 'd' : 's';
|
||||||
if (op == FOP_EXT)
|
if (op == FOP_EXT)
|
||||||
|
@ -47,7 +47,7 @@ void CheatEngine::AddCheat(const std::shared_ptr<CheatBase>& cheat) {
|
|||||||
|
|
||||||
void CheatEngine::RemoveCheat(int index) {
|
void CheatEngine::RemoveCheat(int index) {
|
||||||
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
|
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
|
||||||
if (index < 0 || index >= cheats_list.size()) {
|
if (index < 0 || index >= static_cast<int>(cheats_list.size())) {
|
||||||
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ void CheatEngine::RemoveCheat(int index) {
|
|||||||
|
|
||||||
void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat) {
|
void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat) {
|
||||||
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
|
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
|
||||||
if (index < 0 || index >= cheats_list.size()) {
|
if (index < 0 || index >= static_cast<int>(cheats_list.size())) {
|
||||||
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -64,12 +64,12 @@ void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_c
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CheatEngine::SaveCheatFile() const {
|
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(
|
const std::string filepath = fmt::format(
|
||||||
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
|
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
|
||||||
|
|
||||||
if (!FileUtil::IsDirectory(cheat_dir)) {
|
if (!Common::FS::IsDirectory(cheat_dir)) {
|
||||||
FileUtil::CreateDir(cheat_dir);
|
Common::FS::CreateDir(cheat_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream file;
|
std::ofstream file;
|
||||||
@ -84,15 +84,15 @@ void CheatEngine::SaveCheatFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CheatEngine::LoadCheatFile() {
|
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(
|
const std::string filepath = fmt::format(
|
||||||
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
|
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
|
||||||
|
|
||||||
if (!FileUtil::IsDirectory(cheat_dir)) {
|
if (!Common::FS::IsDirectory(cheat_dir)) {
|
||||||
FileUtil::CreateDir(cheat_dir);
|
Common::FS::CreateDir(cheat_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil::Exists(filepath))
|
if (!Common::FS::Exists(filepath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto gateway_cheats = GatewayCheat::LoadFile(filepath);
|
auto gateway_cheats = GatewayCheat::LoadFile(filepath);
|
||||||
|
@ -248,7 +248,7 @@ System::ResultStatus System::SingleStep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
||||||
FileUtil::SetCurrentRomPath(filepath);
|
Common::FS::SetCurrentRomPath(filepath);
|
||||||
app_loader = Loader::GetLoader(filepath);
|
app_loader = Loader::GetLoader(filepath);
|
||||||
if (!app_loader) {
|
if (!app_loader) {
|
||||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||||
@ -314,8 +314,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
|||||||
|
|
||||||
if (Settings::values.custom_textures) {
|
if (Settings::values.custom_textures) {
|
||||||
const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id;
|
const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id;
|
||||||
FileUtil::CreateFullPath(fmt::format(
|
Common::FS::CreateFullPath(fmt::format(
|
||||||
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id));
|
"{}textures/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), program_id));
|
||||||
custom_tex_cache->FindCustomTextures(program_id);
|
custom_tex_cache->FindCustomTextures(program_id);
|
||||||
}
|
}
|
||||||
if (Settings::values.preload_textures) {
|
if (Settings::values.preload_textures) {
|
||||||
@ -424,8 +424,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
|||||||
switch (result) {
|
switch (result) {
|
||||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||||
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
||||||
case VideoCore::ResultStatus::ErrorBelowGL43:
|
|
||||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
|
|
||||||
default:
|
default:
|
||||||
return ResultStatus::ErrorVideoCore;
|
return ResultStatus::ErrorVideoCore;
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,6 @@ public:
|
|||||||
ErrorVideoCore, ///< Error in the video core
|
ErrorVideoCore, ///< Error in the video core
|
||||||
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
|
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
|
||||||
/// generic drivers installed
|
/// generic drivers installed
|
||||||
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
|
|
||||||
/// OpenGL 4.3 or higher
|
|
||||||
ErrorSavestate, ///< Error saving or loading
|
ErrorSavestate, ///< Error saving or loading
|
||||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||||
ErrorUnknown ///< Any other error
|
ErrorUnknown ///< Any other error
|
||||||
|
@ -301,10 +301,6 @@ private:
|
|||||||
std::vector<std::shared_ptr<Timer>> timers;
|
std::vector<std::shared_ptr<Timer>> timers;
|
||||||
Timer* current_timer = nullptr;
|
Timer* current_timer = nullptr;
|
||||||
|
|
||||||
// Stores a scaling for the internal clockspeed. Changing this number results in
|
|
||||||
// under/overclocking the guest cpu
|
|
||||||
double cpu_clock_scale = 1.0;
|
|
||||||
|
|
||||||
// When true, the event queue can't be modified. Used while deserializing to workaround
|
// When true, the event queue can't be modified. Used while deserializing to workaround
|
||||||
// destructor side effects.
|
// destructor side effects.
|
||||||
bool event_queue_locked = false;
|
bool event_queue_locked = false;
|
||||||
|
@ -45,14 +45,14 @@ void CustomTexCache::FindCustomTextures(u64 program_id) {
|
|||||||
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
|
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
|
||||||
|
|
||||||
const std::string load_path = fmt::format(
|
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)) {
|
if (Common::FS::Exists(load_path)) {
|
||||||
FileUtil::FSTEntry texture_dir;
|
Common::FS::FSTEntry texture_dir;
|
||||||
std::vector<FileUtil::FSTEntry> textures;
|
std::vector<Common::FS::FSTEntry> textures;
|
||||||
// 64 nested folders should be plenty for most cases
|
// 64 nested folders should be plenty for most cases
|
||||||
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
|
Common::FS::ScanDirectoryTree(load_path, texture_dir, 64);
|
||||||
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
|
Common::FS::GetAllFilesFromNestedEntries(texture_dir, textures);
|
||||||
|
|
||||||
for (const auto& file : textures) {
|
for (const auto& file : textures) {
|
||||||
if (file.isDirectory)
|
if (file.isDirectory)
|
||||||
|
@ -412,7 +412,7 @@ bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout&
|
|||||||
|
|
||||||
InitializeFFmpegLibraries();
|
InitializeFFmpegLibraries();
|
||||||
|
|
||||||
if (!FileUtil::CreateFullPath(path)) {
|
if (!Common::FS::CreateFullPath(path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace FileSys {
|
|||||||
*/
|
*/
|
||||||
class FixSizeDiskFile : public DiskFile {
|
class FixSizeDiskFile : public DiskFile {
|
||||||
public:
|
public:
|
||||||
FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode,
|
FixSizeDiskFile(Common::FS::IOFile&& file, const Mode& mode,
|
||||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||||
: DiskFile(std::move(file), mode, std::move(delay_generator_)) {
|
: DiskFile(std::move(file), mode, std::move(delay_generator_)) {
|
||||||
size = GetSize();
|
size = GetSize();
|
||||||
@ -144,7 +144,7 @@ public:
|
|||||||
break; // Expected 'success' case
|
break; // Expected 'success' case
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtil::IOFile file(full_path, "r+b");
|
Common::FS::IOFile file(full_path, "r+b");
|
||||||
if (!file.IsOpen()) {
|
if (!file.IsOpen()) {
|
||||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
||||||
return ERROR_FILE_NOT_FOUND;
|
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,
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
|
||||||
u64 program_id) {
|
u64 program_id) {
|
||||||
std::string fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + "user/";
|
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.
|
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
|
||||||
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
||||||
if (!shared) {
|
if (!shared) {
|
||||||
@ -270,12 +270,12 @@ ResultCode ArchiveFactory_ExtSaveData::Format(const Path& path,
|
|||||||
// These folders are always created with the ExtSaveData
|
// These folders are always created with the ExtSaveData
|
||||||
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
|
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
|
||||||
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
|
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
|
||||||
FileUtil::CreateFullPath(user_path);
|
Common::FS::CreateFullPath(user_path);
|
||||||
FileUtil::CreateFullPath(boss_path);
|
Common::FS::CreateFullPath(boss_path);
|
||||||
|
|
||||||
// Write the format metadata
|
// Write the format metadata
|
||||||
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "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()) {
|
if (!file.IsOpen()) {
|
||||||
// TODO(Subv): Find the correct error code
|
// 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,
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
|
||||||
u64 program_id) const {
|
u64 program_id) const {
|
||||||
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
|
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()) {
|
if (!file.IsOpen()) {
|
||||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
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,
|
void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, const u8* icon_data,
|
||||||
std::size_t icon_size) {
|
std::size_t icon_size) {
|
||||||
std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path);
|
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);
|
icon_file.WriteBytes(icon_data, icon_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,14 +97,14 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa
|
|||||||
return ERROR_NOT_FOUND;
|
return ERROR_NOT_FOUND;
|
||||||
} else {
|
} else {
|
||||||
// Create the file
|
// Create the file
|
||||||
FileUtil::CreateEmptyFile(full_path);
|
Common::FS::CreateEmptyFile(full_path);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PathParser::FileFound:
|
case PathParser::FileFound:
|
||||||
break; // Expected 'success' case
|
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()) {
|
if (!file.IsOpen()) {
|
||||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
||||||
return ERROR_NOT_FOUND;
|
return ERROR_NOT_FOUND;
|
||||||
@ -141,7 +141,7 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const {
|
|||||||
break; // Expected 'success' case
|
break; // Expected 'success' case
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileUtil::Delete(full_path)) {
|
if (Common::FS::Delete(full_path)) {
|
||||||
return RESULT_SUCCESS;
|
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 src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||||
const auto dest_path_full = path_parser_dest.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;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,12 +218,12 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
|
|||||||
}
|
}
|
||||||
|
|
||||||
ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
|
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 {
|
ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
return DeleteDirectoryHelper(
|
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 {
|
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) {
|
if (size == 0) {
|
||||||
FileUtil::CreateEmptyFile(full_path);
|
Common::FS::CreateEmptyFile(full_path);
|
||||||
return RESULT_SUCCESS;
|
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)
|
// 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.
|
// 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) {
|
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
|
break; // Expected 'success' case
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileUtil::CreateDir(mount_point + path.AsString())) {
|
if (Common::FS::CreateDir(mount_point + path.AsString())) {
|
||||||
return RESULT_SUCCESS;
|
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 src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||||
const auto dest_path_full = path_parser_dest.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;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ bool ArchiveFactory_SDMC::Initialize() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil::CreateFullPath(sdmc_directory)) {
|
if (!Common::FS::CreateFullPath(sdmc_directory)) {
|
||||||
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ bool ArchiveFactory_SDMCWriteOnly::Initialize() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil::CreateFullPath(sdmc_directory)) {
|
if (!Common::FS::CreateFullPath(sdmc_directory)) {
|
||||||
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_direc
|
|||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) {
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) {
|
||||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, 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
|
// 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.
|
// 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
|
// 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,
|
ResultCode ArchiveSource_SDSaveData::Format(u64 program_id,
|
||||||
const FileSys::ArchiveFormatInfo& format_info) {
|
const FileSys::ArchiveFormatInfo& format_info) {
|
||||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
||||||
FileUtil::DeleteDirRecursively(concrete_mount_point);
|
Common::FS::DeleteDirRecursively(concrete_mount_point);
|
||||||
FileUtil::CreateFullPath(concrete_mount_point);
|
Common::FS::CreateFullPath(concrete_mount_point);
|
||||||
|
|
||||||
// Write the format metadata
|
// Write the format metadata
|
||||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
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()) {
|
if (file.IsOpen()) {
|
||||||
file.WriteBytes(&format_info, sizeof(format_info));
|
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 {
|
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const {
|
||||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
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()) {
|
if (!file.IsOpen()) {
|
||||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
||||||
|
@ -58,7 +58,7 @@ ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string&
|
|||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path,
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path,
|
||||||
u64 program_id) {
|
u64 program_id) {
|
||||||
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
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
|
// TODO(Subv): Check error code, this one is probably wrong
|
||||||
return ERR_NOT_FORMATTED;
|
return ERR_NOT_FORMATTED;
|
||||||
}
|
}
|
||||||
@ -70,8 +70,8 @@ ResultCode ArchiveFactory_SystemSaveData::Format(const Path& path,
|
|||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id) {
|
||||||
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
||||||
FileUtil::DeleteDirRecursively(fullpath);
|
Common::FS::DeleteDirRecursively(fullpath);
|
||||||
FileUtil::CreateFullPath(fullpath);
|
Common::FS::CreateFullPath(fullpath);
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
|
Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
|
||||||
FileUtil::IOFile file(filepath, "rb");
|
Common::FS::IOFile file(filepath, "rb");
|
||||||
if (!file.IsOpen())
|
if (!file.IsOpen())
|
||||||
return Loader::ResultStatus::Error;
|
return Loader::ResultStatus::Error;
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ bool DiskFile::Close() const {
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
DiskDirectory::DiskDirectory(const std::string& path) {
|
DiskDirectory::DiskDirectory(const std::string& path) {
|
||||||
directory.size = FileUtil::ScanDirectoryTree(path, directory);
|
directory.size = Common::FS::ScanDirectoryTree(path, directory);
|
||||||
directory.isDirectory = true;
|
directory.isDirectory = true;
|
||||||
children_iterator = directory.children.begin();
|
children_iterator = directory.children.begin();
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ u32 DiskDirectory::Read(const u32 count, Entry* entries) {
|
|||||||
u32 entries_read = 0;
|
u32 entries_read = 0;
|
||||||
|
|
||||||
while (entries_read < count && children_iterator != directory.children.cend()) {
|
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;
|
const std::string& filename = file.virtualName;
|
||||||
Entry& entry = entries[entries_read];
|
Entry& entry = entries[entries_read];
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ u32 DiskDirectory::Read(const u32 count, Entry* entries) {
|
|||||||
break;
|
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_directory = file.isDirectory;
|
||||||
entry.is_hidden = (filename[0] == '.');
|
entry.is_hidden = (filename[0] == '.');
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user