Compare commits

..

247 Commits

Author SHA1 Message Date
Lioncash
20800f2df7 maxwell3d: Move FinishedPrimitiveBatch event after AcceleratedDrawBatch()
The start and finish events should likely not be right after one another
like this, otherwise the batch will appear to complete immediately
2018-08-24 19:58:05 -04:00
Tobias
165c23c848 Port #4013 from Citra: "Init logging sooner so we dont miss some logs on startup" (#1142)
* Port #4013 from Citra: "Init logging sooner so we dont miss some logs on startup"

* Fix compilation
2018-08-23 19:52:06 -04:00
David
5049ca5d8c Added GetBootMode (#1107)
* Added GetBootMode

Used by homebrew

* Added enum for GetBootMode
2018-08-23 18:31:45 -04:00
bunnei
0dce6d7008 Merge pull request #1160 from bunnei/surface-reserve
gl_rasterizer_cache: Several improvements
2018-08-23 12:04:37 -04:00
bunnei
3ed0115e36 Merge pull request #1153 from bunnei/stencil-clear
gl_rasterizer: Implement partial color clear, stencil clear, and stencil test.
2018-08-23 12:04:08 -04:00
bunnei
d65f079cc1 gl_rasterizer_cache: Blit when possible on RecreateSurface. 2018-08-23 11:27:01 -04:00
bunnei
fee8bdd90c gl_rasterizer_cache: Reserve surfaces that have already been created for later use. 2018-08-23 11:27:01 -04:00
bunnei
fde2017a3f gl_rasterizer_cache: Remove assert for RecreateSurface type. 2018-08-23 11:27:00 -04:00
bunnei
ebf5768340 gl_rasterizer_cache: Implement compressed texture copies. 2018-08-23 11:27:00 -04:00
bunnei
a4ac3bed6c gl_rasterizer: Implement stencil test.
- Used by Splatoon 2.
2018-08-23 11:08:49 -04:00
bunnei
da3da6be90 gl_rasterizer: Implement partial color clear and stencil clear. 2018-08-23 11:08:48 -04:00
bunnei
2a472ff54d maxwell_3d: Update to include additional stencil registers. 2018-08-23 11:08:47 -04:00
bunnei
c4ed0b16b1 gl_state: Update to handle stencil front/back face separately. 2018-08-23 11:08:46 -04:00
bunnei
c7f2fb2151 Merge pull request #1157 from lioncash/vec
gl_shader_gen: Use a std::vector to represent program code instead of std::array
2018-08-23 02:19:00 -04:00
bunnei
232b0d9d2a Merge pull request #1156 from Lakumakkara/lop3
gl_shader_decompiler: Implement LOP3
2018-08-23 02:16:49 -04:00
literalmente-game
74e08b4800 Swap "Plus" with "Minus" on the controller GUI (#1150)
* Swap "Plus" with "Minus" on the controller GUI

Major fix /s
2018-08-22 18:47:07 -06:00
James Rowe
a1bdc597e9 Merge pull request #1159 from lioncash/fmt
externals: Update fmt to 6201052
2018-08-22 18:46:22 -06:00
bunnei
c5ea6db02d Merge pull request #1137 from lioncash/namespace
renderer_opengl: Namespace OpenGL code
2018-08-22 18:14:48 -04:00
James Rowe
c7c4e6dcba Merge pull request #1158 from lioncash/boost
externals/boost: Update to 1.68.0
2018-08-22 15:38:12 -06:00
Lioncash
c5c0da41b4 externals: Update fmt to 6201052
Previously, we'd get warnings like:

"
c:\projects\yuzu\externals\fmt\include\fmt\format.h(2868): warning
C4127: conditional expression is constant
[C:\projects\yuzu\msvc_build\externals\dynarmic\src\dynarmic.vcxproj]
"

spamming the build output when compiling on Windows. This updates fmt to
include the upstreamed fix that silences this warning.
2018-08-22 17:32:53 -04:00
Lioncash
f835349364 externals/boost: Update to 1.68.0
This updates the submodule to use 1.68.0. Notably, it gets rid of the
silly

"Info: Boost.Config is older than your compiler version - probably
nothing bad will happen - but you may wish to look for an update Boost
version. Define BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE to suppress this
message."

message that spams the output of the build process on Windows.
2018-08-22 17:15:44 -04:00
Lioncash
12ba80a86c gl_shader_gen: Make ShaderSetup's constructor explicit
Prevents implicit conversions.
2018-08-22 17:04:44 -04:00
Lioncash
1fd979f50a gl_shader_gen: Use a std::vector to represent program code instead of std::array
While convenient as a std::array, it's also quite a large set of data as
well (32KB). It being an array also means data cannot be std::moved. Any
situation where the code is being set or relocated means that a full
copy of that 32KB data must be done.

If we use a std::vector we do need to allocate on the heap, however, it
does allow us to std::move the data we have within the std::vector into
another std::vector instance, eliminating the need to always copy the
program data (as std::move in this case would just transfer the pointers
and bare necessities over to the new vector instance).
2018-08-22 17:04:44 -04:00
Laku
b2ca8089ce more fixes 2018-08-23 00:01:40 +03:00
Laku
e70a3c5a5d fixes 2018-08-22 21:33:32 +03:00
bunnei
d1b1c42c07 Merge pull request #1155 from tech4me/icon-fix
config: Fixed icon size get set to 0
2018-08-22 13:45:22 -04:00
Lioncash
dd35b4b18a renderer_opengl: Namespace OpenGL code
Namespaces all OpenGL code under the OpenGL namespace.

Prevents polluting the global namespace and allows clear distinction
between other renderers' code in the future.
2018-08-22 06:14:47 -04:00
Laku
4877e6c2f6 remove debug logging 2018-08-22 11:45:28 +03:00
Laku
8e8326595f implement lop3 2018-08-22 10:09:44 +03:00
tech4me
8ce02d85e9 config: Fixed icon size get set to 0 2018-08-21 22:36:29 -07:00
bunnei
b38d67d940 Merge pull request #1136 from tech4me/master
qt/main: Port part of citra(#3411), open savedata works
2018-08-22 01:30:08 -04:00
bunnei
cea627b0fc Merge pull request #840 from FearlessTobi/port-3353
Port #3353 from Citra: "citra-qt: Add customizable speed limit target "
2018-08-22 01:19:50 -04:00
bunnei
5abf71fe65 Merge pull request #1154 from OatmealDome/topology-lines
maxwell_to_gl: Implement PrimitiveTopology::Lines
2018-08-22 01:08:34 -04:00
bunnei
eef0c93643 Merge pull request #1141 from FearlessTobi/port-3902
Port #3902 from Citra: "Add restart hotkey & menu option"
2018-08-22 01:07:59 -04:00
bunnei
125d7122ac Merge pull request #1124 from Subv/logic_ops
GPU: Implemented logic ops.
2018-08-22 01:05:25 -04:00
OatmealDome
ad1220e1b3 maxwell_to_gl: Implement PrimitiveTopology::Lines
Used by Splatoon 2's debug menu.
2018-08-22 01:01:06 -04:00
bunnei
92b85fad70 Merge pull request #1147 from lioncash/warn
logging/text_formatter: Use empty braces for initializing CONSOLE_SCREEN_BUFFER_INFO instance
2018-08-22 00:37:59 -04:00
bunnei
cb8b371570 Merge pull request #1151 from bunnei/revert-4a2ee191
Revert "Shader: Use the right sampler type in the TEX, TEXS and TLDS …"
2018-08-22 00:37:30 -04:00
bunnei
38517241ec Merge pull request #1152 from ogniK5377/plu-include
Added missing include for pl:u
2018-08-21 22:41:09 -04:00
David Marcec
15cc34b93e Added missing include for pl:u
Should fix any compile errors
2018-08-22 12:39:52 +10:00
David
99fc32428a PL:U Added BFTTF loading(Loading from System NAND dumps) (#1088)
* Added bfttf loading

We can now load system bfttf fonts from system archives AND shared memory dumps. This allows people who have installed their system nand dumps to yuzu to automatically get shared font support. We also now don't hard code the offsets or the sizes of the shared fonts and it's all calculated for us now.

* Addressed plu fixups

* Style changes for plu

* Fixed logic error for plu and added more error checks.
2018-08-21 21:31:49 -04:00
bunnei
d63b1d21f1 Revert "Shader: Use the right sampler type in the TEX, TEXS and TLDS instructions."
- This reverts commit 3ef4b3d4b4.
- This commit had broken a lot of games. We really should do a full implementation of this in one change.
2018-08-21 20:07:40 -04:00
bunnei
ac68c8a605 Merge pull request #1145 from lioncash/fwd-decl
vfs: Replace mode.h include with forward declarations where applicable
2018-08-21 18:00:28 -04:00
bunnei
c2695aa2eb Merge pull request #1146 from lioncash/am
am: Utilize std::array within PopLaunchParameter()
2018-08-21 18:00:06 -04:00
bunnei
16b83fac9b Merge pull request #1148 from lioncash/audio-warn
audio_core/filter: Add explicit cast to assignment in Process()
2018-08-21 17:04:53 -04:00
bunnei
a769d8c913 Merge pull request #1149 from lioncash/paren
shader_bytecode: Parenthesize conditional expression within GetTextureType()
2018-08-21 17:04:29 -04:00
Lioncash
a0e2bd85a5 shader_bytecode: Parenthesize conditional expression within GetTextureType()
Resolves a -Wlogical-op-parentheses warning.
2018-08-21 15:08:35 -04:00
Lioncash
29ac15d1b8 vfs: Replace mode.h include with forward declarations where applicable
Avoids the need to rebuild these source files if the mode header
changes.
2018-08-21 15:06:42 -04:00
Lioncash
0057a47e41 audio_core/filter: Add explicit cast to assignment in Process()
Previously this would cause warnings about implicit conversions to s16
from a double
2018-08-21 12:32:37 -04:00
Lioncash
5a53d75313 logging/text_formatter: Use empty braces for initializing CONSOLE_SCREEN_BUFFER_INFO instance
The previous form of initializing done here is a C-ism, an empty set of
braces is sufficient for initializing (and doesn't potentially cause
missing brace warnings, given the first member of the struct is a COORD
struct).
2018-08-21 11:31:05 -04:00
Lioncash
8dd9cb98ce am: Utilize std::array within PopLaunchParameter()
Gets rid of the potential for C array-to-pointer decay, and also makes
pointer arithmetic to get the end of the copy range unnecessary. We can
just use std::array's begin() and end() member functions.
2018-08-21 11:03:14 -04:00
bunnei
c95c4442e9 Merge pull request #1143 from lioncash/inc
sdmc_factory: Remove unnecessary core include
2018-08-21 10:22:29 -04:00
bunnei
37f2ec6fc2 Merge pull request #1139 from lioncash/bitfield
bit_field: Convert ToBool() into explicit operator bool
2018-08-21 10:21:19 -04:00
bunnei
624239ed6b Merge pull request #1140 from FearlessTobi/port-4056
Port #4056 from Citra: "Add Clear Recent Files menu action"
2018-08-21 10:20:56 -04:00
Mat M
5678ec0dd0 Merge pull request #1144 from MerryMage/MAX_LAG_TIME_US
perf_stats: Change MAX_LAG_TIME_US to an appropriate value
2018-08-21 09:51:46 -04:00
MerryMage
3f4fb4b037 perf_stats: Change MAX_LAG_TIME_US to an appropriate value
25us is far too small, and would result in std::this_thread::sleep_for
being called with this as a maximum value. This means that a guest
application that produces frames instantly would only be limited to
40 kHz.

25ms is a more appropriate value, as it allows for a 60 Hz refresh
rate while providing enough slack in the negative region.
2018-08-21 14:50:50 +01:00
Lioncash
bfb28c5b3f sdmc_factory: Remove unnecessary core include
This doesn't require the central core header to be included, it just
needs the vfs headers.
2018-08-21 07:54:29 -04:00
fearlessTobi
f2d5b100c2 Port #3902 from Citra: "Add restart hotkey & menu option" 2018-08-21 13:24:55 +02:00
fearlessTobi
6923ecee3a Port #4056 from Citra: "Add Clear Recent Files menu action" 2018-08-21 13:12:45 +02:00
Lioncash
36090521ce bit_field: Convert ToBool() into explicit operator bool
Gets rid of a TODO that is long overdue.
2018-08-21 06:39:45 -04:00
tech4me
cc71832b19 qt/main: Port part of citra(#3411), open savedata works 2018-08-21 02:04:33 -07:00
bunnei
bf89a99839 Merge pull request #1123 from lioncash/screen
rasterizer_interface: Remove renderer-specific ScreenInfo type from AccelerateDraw() in RasterizerInterface
2018-08-21 01:18:34 -04:00
bunnei
79243b6fa0 Merge pull request #1129 from lioncash/header
romfs_factory, service/filesystem: Use forward declarations where applicable
2018-08-21 01:18:04 -04:00
bunnei
b0f7713fce Merge pull request #1132 from Subv/gl_FragDepth
Shaders: Implement depth writing in fragment shaders.
2018-08-21 01:17:53 -04:00
bunnei
8c9abe1d41 Merge pull request #1134 from lioncash/log
renderer_opengl: Use LOG_DEBUG for GL_DEBUG_SEVERITY_NOTIFICATION and GL_DEBUG_SEVERITY_LOW logs
2018-08-21 01:17:31 -04:00
bunnei
ca58929eb0 Merge pull request #1121 from Subv/tex_reinterpret
Rasterizer: Use PBOs to reinterpret texture formats when games re-use the same memory.
2018-08-21 01:06:40 -04:00
Lioncash
523e4be02c renderer_opengl: Use LOG_DEBUG for GL_DEBUG_SEVERITY_NOTIFICATION and GL_DEBUG_SEVERITY_LOW logs
LOG_TRACE is only enabled on debug builds which can be quite slow when
trying to debug graphics issues. Instead we can log the messages to the
debug log, which is available on both release and debug builds.
2018-08-21 00:23:09 -04:00
bunnei
fde3b1b6f2 Merge pull request #1133 from lioncash/guard
gl_stream_buffer: Add missing header guard
2018-08-20 23:37:55 -04:00
Lioncash
477eee3993 service/filesystem: Use forward declarations where applicable
Avoids the need to rebuild multiple source files if the filesystem code
headers change.

This also gets rid of a few instances of indirect inclusions being
relied upon
2018-08-20 23:28:46 -04:00
Lioncash
93a4097e9d gl_stream_buffer: Add missing header guard
Prevents potential compilation errors from occuring due to multiple
inclusions
2018-08-20 23:25:08 -04:00
Subv
e3bddf8137 Shaders: Implement depth writing in fragment shaders.
We'll write <last color output reg + 2> to gl_FragDepth.
2018-08-20 21:57:56 -05:00
bunnei
c4ce7e456a Merge pull request #1126 from lioncash/telem
telemetry_session: Don't allocate std::string instances for program lifetime in GetTelemetryId() and RegenerateTelemetryId()
2018-08-20 22:15:56 -04:00
bunnei
e33452f7e8 Merge pull request #1131 from bunnei/impl-tex3d-texcube
gl_shader_decompiler: Implement TextureCube/Texture3D for TEX/TEXS.
2018-08-20 22:15:18 -04:00
bunnei
5aaee2ff8d Merge pull request #1106 from Subv/multiple_rendertargets
Shaders: Write all the enabled color outputs when a fragment shader exits.
2018-08-20 21:56:06 -04:00
bunnei
2ae88feea7 shader_bytecode: Replace some UNIMPLEMENTED logs. 2018-08-20 21:53:49 -04:00
bunnei
16db8b9d9f gl_shader_decompiler: Implement Texture3D for TEXS. 2018-08-20 21:53:18 -04:00
bunnei
948002635f gl_shader_decompiler: Implement TextureCube for TEX. 2018-08-20 21:53:00 -04:00
bunnei
ea99819f37 Merge pull request #1130 from Subv/tex_2d
Shaders: Fixed texture coordinates in TEX with Texture2D
2018-08-20 21:49:47 -04:00
Subv
eac3cf301c Shaders: Fixed the coords in TEX with Texture2D.
The X and Y coordinates should be in gpr8 and gpr8+1, respectively.

This fixes the cutscene rendering in Sonic Mania.
2018-08-20 20:45:46 -05:00
Subv
fc5b489b0f Shaders: Log and crash when using an unimplemented texture type in a texture sampling instruction. 2018-08-20 20:44:56 -05:00
bunnei
19b05c3f55 Merge pull request #1122 from lioncash/acc
acc/profile_manager: General cleanup
2018-08-20 20:54:34 -04:00
bunnei
2788144f46 Merge pull request #1125 from bunnei/update-dynarmic
externals: Update dynarmic to a42f301c.
2018-08-20 20:46:16 -04:00
Lioncash
96463d0a55 romfs_factory: Remove unnecessary includes and use forward declarations where applicable
Avoids the need to rebuild whatever includes the romfs factory header if
the loader header ever changes. We also don't need to include the main
core header. We can instead include the headers we specifically need.
2018-08-20 20:27:00 -04:00
bunnei
dd70ddad7e Merge pull request #1095 from DarkLordZach/sysarchives
filesystem: Add support for loading of system archives
2018-08-20 20:17:57 -04:00
James Rowe
c0fb321935 Merge pull request #1127 from yuzu-emu/revert-838-port-3616
Revert "Port #3616 from Citra: "appveyor: set jobs to 4 for mingw""
2018-08-20 18:14:54 -06:00
Zach Hilman
34f3d58470 Revert "Port #3616 from Citra: "appveyor: set jobs to 4 for mingw"" 2018-08-20 20:13:28 -04:00
Lioncash
b5fb246a99 telemetry_session: Don't allocate std::string instances for program lifetime in GetTelemetryId() and RegenerateTelemetryId()
Given these functions aren't intended to be used frequently, there's no
need to keep the std::string instances allocated for the whole lifetime
of the program. It's just a waste of memory.
2018-08-20 20:06:25 -04:00
bunnei
c6fda4c758 externals: Update dynarmic to a42f301c. 2018-08-20 19:50:49 -04:00
Lioncash
609cb04f3f acc: Replace profile_manager include with a forward declaration
This is only used in a shared_ptr, so we can forward declare it.
2018-08-20 19:48:57 -04:00
Lioncash
eb88fedc5d acc: Simplify WriteBuffer call within LoadImage()
We have an overload of WriteBuffer that accepts containers that satisfy
the ContiguousContainer concept, which std::array does, so we only need
to pass in the array itself.
2018-08-20 19:48:57 -04:00
Lioncash
f5b132676f acc: Correct IProfile's constructor initializer list order
Arranges them in the order the members would be initialized
2018-08-20 19:48:57 -04:00
Lioncash
0fcdf37917 acc: Remove unused DEFAULT_USER_ID
This is no longer used, so it can be removed.
2018-08-20 19:48:57 -04:00
Lioncash
350f6e0aa4 profile_manager: Use INVALID_UUID in the initializer of last_opened_user
Makes it a little bit more self-documenting.
2018-08-20 19:48:57 -04:00
Lioncash
9d8f19d7bf profile_manager: Remove unnecessary memcpy in GetProfileBaseAndData()
Given the source and destination types are the same std::array type, we
can simply use regular assignment to perform the same behavior.
2018-08-20 19:48:57 -04:00
Lioncash
38cd4e9c61 profile_manager: Use type aliases for username data, profile data, and user arrays
Avoids the need to repeatedly specify the whole array type in multiple
places.
2018-08-20 19:48:57 -04:00
Lioncash
f9a26d468c profile_manager: Take ProfileInfo by const reference where applicable
ProfileInfo is quite a large struct in terms of data, and we don't need
to perform a copy in these instances, so we can just pass constant
references instead.
2018-08-20 19:48:57 -04:00
Lioncash
1277556c69 profile_manager: Make array parameter to CreateNewUser a const reference
This doesn't modify the passed in array, so this can be a const
reference.
2018-08-20 19:48:57 -04:00
Lioncash
dfdf4a46fe profile_manager: Remove unnecessary static
This can just be constexpr like the others
2018-08-20 19:48:57 -04:00
Lioncash
69dd37d874 profile_manager: Simplify UUID's two param constructor, operator==, and operator bool
We can use the constructor initializer list and just compare the
contained u128's together instead of comparing each element
individually. Ditto for comparing against an invalid UUID.
2018-08-20 19:48:57 -04:00
Lioncash
f13a66b963 profile_manager: Move UUID generation function to the cpp file
This avoids needing to dump the contents of <random> into other files
that include the profile manager header.
2018-08-20 19:48:53 -04:00
Subv
2b9eee4d1e GPU: Implemented the logic op functionality of the GPU.
This will ASSERT if blending is enabled at the same time as logic ops.
2018-08-20 18:44:47 -05:00
bunnei
b1d238bbb8 Merge pull request #1064 from lioncash/telemetry
common/telemetry: Migrate core-independent info gathering to common
2018-08-20 19:43:17 -04:00
Subv
f24ab6d9e6 GLState: Allow enabling/disabling GL_COLOR_LOGIC_OP independently from blending. 2018-08-20 18:43:11 -05:00
Lioncash
46ef072cf9 rasterizer_interface: Remove ScreenInfo from AccelerateDraw()'s signature
This is an OpenGL renderer-specific data type. Given that, this type
shouldn't be used within the base interface for the rasterizer. Instead,
we can pass this information to the rasterizer via reference.
2018-08-20 19:43:05 -04:00
Subv
6bcdf37d4f GPU: Added registers for the logicop functionality. 2018-08-20 18:42:36 -05:00
Lioncash
bc16f7f3cc renderer_base: Make creation of the rasterizer, the responsibility of the renderers themselves
Given we use a base-class type within the renderer for the rasterizer
(RasterizerInterface), we want to allow renderers to perform more
complex initialization if they need to do such a thing. This makes it
important to reserve type information.

Given the OpenGL renderer is quite simple settings-wise, this is just a
simple shuffling of the initialization code. For something like Vulkan
however this might involve doing something like:

// Initialize and call rasterizer-specific function that requires
// the full type of the instance created.
auto raster = std::make_unique<VulkanRasterizer>(some, params);
raster->CallSomeVulkanRasterizerSpecificFunction();

// Assign to base class variable
rasterizer = std::move(raster)
2018-08-20 19:28:00 -04:00
fearlessTobi
ba8ff096fd Port #3353 from Citra 2018-08-21 01:14:06 +02:00
Subv
7784ce1854 Shaders: Write all the enabled color outputs when a fragment shader exits.
We were only writing to the first render target before.
Note that this is only the GLSL side of the implementation, supporting multiple render targets requires more changes in the OpenGL renderer.

Dual Source blending is not implemented and stuff that uses it might not work at all.
2018-08-20 17:31:25 -05:00
Zach Hilman
e8cb6f5c9b registration: Add Data_Unknown5 NCAContentType 2018-08-20 17:34:18 -04:00
Lioncash
9e9a4bb3a7 profile_manager: Remove unnecessary std::move in AddToProfiles() and CreateNewUser()
Moving a const reference isn't possible, so this just results in a copy
(and given ProfileInfo is composed of trivial types and aggregates, a
move wouldn't really do anything).
2018-08-20 17:18:31 -04:00
Subv
d7c68fbb12 Rasterizer: Reinterpret the raw texture bytes instead of blitting (and thus doing format conversion) to a new texture when a game requests an old texture address with a different format. 2018-08-20 15:20:35 -05:00
Subv
3fe77be392 Rasterizer: Don't attempt to copy over the old texture's data when doing a format reinterpretation if we're only going to clear the framebuffer. 2018-08-20 15:20:35 -05:00
bunnei
028d90eb79 Merge pull request #1104 from Subv/instanced_arrays
GLRasterizer: Implemented instanced vertex arrays.
2018-08-20 14:32:50 -04:00
bunnei
296e57fa0e Merge pull request #1115 from Subv/texs_mask
Shaders/TEXS: Write to the correct output register when swizzling.
2018-08-20 14:31:33 -04:00
bunnei
b20ed93884 Merge pull request #1112 from Subv/sampler_types
Shaders: Use the correct shader type when sampling textures.
2018-08-20 14:30:45 -04:00
bunnei
185b35bfcd Merge pull request #1117 from ogniK5377/CheckFreeCommunicationPermission
Added CheckFreeCommunicationPermission
2018-08-20 11:00:26 -04:00
bunnei
943771e703 Merge pull request #1017 from ogniK5377/better-account
New account backend to allow for future extended support
2018-08-20 10:59:15 -04:00
bunnei
ce4b77bd7d Merge pull request #1120 from ogniK5377/rgba8-uint
Implemented RGBA8_UINT
2018-08-20 10:54:51 -04:00
bunnei
6ee8b15abe Merge pull request #1119 from lioncash/uninit
game_list: Avoid uninitialized variables when retrieving program ID
2018-08-20 10:53:52 -04:00
David Marcec
23d45715dc Implemented RGBA8_UINT
Needed by kirby
2018-08-20 22:26:54 +10:00
Lioncash
ffd60ee476 game_list: Avoid uninitialized variables when retrieving program ID
Avoids potentially leaving this variable uninitialized based off the
loader failing to retrieve the ID value.
2018-08-20 04:23:05 -04:00
David Marcec
8a88110060 Added CheckFreeCommunicationPermission
This fixes save files not loading in splatoon 2
2018-08-20 18:14:49 +10:00
Subv
6cf719a4ab Shaders/TEXS: Fixed the component mask in the TEXS instruction.
Previously we could end up with a TEXS that didn't write any outputs, this was wrong.
2018-08-19 17:09:40 -05:00
bunnei
51ddb130c5 Merge pull request #1089 from Subv/neg_bits
Shaders: Corrected the 'abs' and 'neg' bit usage in the float arithmetic instructions.
2018-08-19 17:01:48 -04:00
bunnei
9b17486be6 Merge pull request #1105 from Subv/convert_neg
Shader: Remove an unneeded assert, the negate bit is implemented for conversion instructions.
2018-08-19 17:01:20 -04:00
bunnei
0a1d4fbc5c Merge pull request #1113 from Subv/texs_mask
Shaders/TEXS: Fixed the component mask in the TEXS instruction.
2018-08-19 17:00:59 -04:00
Subv
f7edbcd7a3 Shaders/TEXS: Fixed the component mask in the TEXS instruction.
Previously we could end up with a TEXS that didn't write any outputs, this was wrong.
2018-08-19 14:00:12 -05:00
bunnei
b0eb580931 Merge pull request #1102 from ogniK5377/mirror-clamp-edge
Added WrapMode MirrorOnceClampToEdge
2018-08-19 13:59:41 -04:00
bunnei
85da529f15 Merge pull request #1101 from Subv/ssy_stack
Shaders: Implemented a stack for the SSY/SYNC instructions.
2018-08-19 13:58:45 -04:00
Subv
7fb406c3fc Shader: Implemented the TLD4 and TLD4S opcodes using GLSL's textureGather.
It is unknown how TLD4S determines the sampler type, more research is needed.
2018-08-19 12:57:58 -05:00
Subv
3ef4b3d4b4 Shader: Use the right sampler type in the TEX, TEXS and TLDS instructions.
Different sampler types have their parameters in different registers.
2018-08-19 12:57:54 -05:00
Subv
73b937b190 Shader: Added bitfields for the texture type of the various sampling instructions. 2018-08-19 12:57:51 -05:00
Subv
656758fd81 Shaders: Added decodings for TLD4 and TLD4S 2018-08-19 12:57:08 -05:00
bunnei
29d4f8c2dd Merge pull request #1109 from Subv/ldg_decode
Shaders: Added decodings for  the LDG and STG instructions.
2018-08-19 13:31:19 -04:00
bunnei
9baf5de90c Merge pull request #1108 from Subv/front_facing
Shaders: Implemented the gl_FrontFacing input attribute (attr 63).
2018-08-19 13:21:14 -04:00
bunnei
d6cb22b0df Merge pull request #1103 from Subv/lop_pred
Shader: Implemented the predicate and mode arguments of LOP.
2018-08-19 13:19:16 -04:00
Subv
1b92ae136f Shaders: Added decodings for the LDG and STG instructions. 2018-08-19 00:46:34 -05:00
Subv
731701a2d2 Shaders: Implemented the gl_FrontFacing input attribute (attr 63). 2018-08-19 00:14:34 -05:00
David Marcec
706fc5d2d6 Added check to see if ARB_texture_mirror_clamp_to_edge is supported 2018-08-19 12:00:33 +10:00
Zach Hilman
27da7bc9da filesystem: Add support for loading of system archives 2018-08-18 21:28:23 -04:00
Subv
9b1c49a9cf Shader: Remove an unneeded assert, the negate bit is implemented for conversion instructions. 2018-08-18 14:48:05 -05:00
Subv
e0f66c1fbf GLRasterizer: Implemented instanced vertex arrays.
Before each draw call, for every enabled vertex array configured as instanced, we take the current instance id and divide it by its configured divisor, then we multiply that by the corresponding stride and increment the start address by the resulting amount. This way we can simulate the vertex array being incremented once per instance without actually using OpenGL's instancing functions.
2018-08-18 14:42:26 -05:00
Subv
8335b2f115 Shader: Implemented the predicate and mode arguments of LOP.
The mode can be used to set the predicate to true depending on the result of the logic operation. In some cases, this means discarding the result (writing it to register 0xFF (Zero)).

This is used by Super Mario Odyssey.
2018-08-18 14:36:37 -05:00
James Rowe
367feaefa0 Merge pull request #838 from FearlessTobi/port-3616
Port #3616 from Citra: "appveyor: set jobs to 4 for mingw"
2018-08-18 11:45:51 -06:00
David Marcec
71cc482bbd Added WrapMode MirrorOnceClampToEdge
Used by splatoon 2
2018-08-19 02:26:50 +10:00
Subv
ff358d97e8 Shaders: Implemented a stack for the SSY/SYNC instructions.
The SSY instruction pushes an address into the stack, and the SYNC instruction pops it. The current stack depth is 20, we should figure out if this is enough or not.
2018-08-18 10:48:12 -05:00
Subv
2e95ba2e9c Shaders: Corrected the 'abs' and 'neg' bit usage in the float arithmetic instructions.
We should definitely audit our shader generator for more errors like this.
2018-08-18 10:22:42 -05:00
bunnei
6eba539f4a Merge pull request #1100 from ogniK5377/missing-pred
Added pred-condition GreaterThanWithNan
2018-08-18 10:32:59 -04:00
David Marcec
63dff47e22 Added predcondition GreaterThanWithNan 2018-08-18 17:49:59 +10:00
bunnei
504cff2b7a Merge pull request #1096 from bunnei/supported-blits
gl_rasterizer_cache: Remove asserts for supported blits.
2018-08-17 22:41:53 -04:00
bunnei
804aebf7c7 Merge pull request #1097 from bunnei/gl-critical
renderer_opengl: Treat OpenGL errors as critical.
2018-08-17 10:39:13 -04:00
greggameplayer
2003771789 Implement SetIdleTimeDetectionExtension & GetIdleTimeDetectionExtension (#1059)
* Used by Mario Tennis Aces
2018-08-17 00:23:08 -04:00
bunnei
f246fd778d Merge pull request #1090 from lioncash/ctor-assign
core: Delete System copy/move constructors and assignment operators
2018-08-17 00:19:55 -04:00
bunnei
1db7839f11 Merge pull request #1091 from lioncash/warning
qt/main: Get rid of compilation warnings
2018-08-17 00:19:05 -04:00
bunnei
224be09d66 Merge pull request #1093 from greggameplayer/GetDefaultDisplayResolutionChangeEvent
Implement GetDefaultDisplayResolutionChangeEvent
2018-08-17 00:18:35 -04:00
bunnei
e341d868ee gl_rasterizer_cache: Remove asserts for supported blits. 2018-08-17 00:10:08 -04:00
bunnei
da7226442f renderer_opengl: Treat OpenGL errors as critical. 2018-08-17 00:09:27 -04:00
bunnei
727136a9c9 Merge pull request #1019 from Subv/vertex_divisor
Rasterizer: Manually implemented instanced rendering.
2018-08-17 00:07:06 -04:00
bunnei
0d9b3e425e Merge pull request #1087 from MerryMage/dynarmic
dynarmic: Update to 550d662
2018-08-16 18:07:11 -04:00
bunnei
ce56b8e1fa Merge pull request #1084 from bunnei/depth
gl_rasterizer_cache: Treat Depth formats differently from DepthStencil.
2018-08-16 18:06:51 -04:00
greggameplayer
cef35e7c9c correct coding style 2018-08-16 23:46:06 +02:00
greggameplayer
928e78dced Implement GetDefaultDisplayResolutionChangeEvent
Require by Toki Tori and Toki Tori 2+
2018-08-16 23:25:54 +02:00
bunnei
3fd78f4d24 Merge pull request #1085 from lioncash/namespace
common: Namespace hex_util.h/.cpp
2018-08-16 11:54:42 -04:00
Lioncash
9791f0d590 qt/main: Unindent code in OnMenuInstallToNAND()
We can change this into an early-return if the filename is empty.
There's no need to include all of the code within the if statement.
2018-08-16 10:37:58 -04:00
Lioncash
2a3d7128d1 qt/main: Make installation dialog text within OnMenuInstallToNAND() translatable
This is user-facing text, so it should be marked as translatable by Qt.
2018-08-16 10:36:42 -04:00
Lioncash
aac807fd3a qt/main: Get rid of compilation warnings
Gets rid of truncation warnings about conversion to int. While we're at
it, we can also de-hardcode the buffer size being used.
2018-08-16 10:28:06 -04:00
Lioncash
a0ce6de913 core: Delete System copy/move constructors and assignment operators
Prevents potentially making copies or doing silly things by accident
with the System instance, particularly given our current core is
designed (unfortunately) around one instantiable instance.

This will prevent the accidental case of:

auto instance = System::Instance();

being compiled without warning when it's supposed to be:

auto& instance = System::Instance();
2018-08-16 10:21:14 -04:00
MerryMage
94329038b6 dynarmic: Update to 550d662
550d662 load_store_exclusive: Define s == t state to be Constraint_NONE
0b69381 A64/translate: Allow for unpredictable behaviour to be defined
6d236d4 system: Implement MRS CNTFRQ_EL0
6cbb6fb A32/testenv: Add missing headers
6729328 externals: Update xbyak to v5.67
1812bd2 Squashed 'externals/xbyak/' changes from 2794cde7..671fc805
9a95802 externals: Document subtrees
714a840 A64: Implement SQ{ADD, SUB}, and UQ{ADD, SUB}'s vector variants
8cab459 A64: Implement UQADD/UQSUB's scalar variants
18a8151 ir: Add opcodes for unsigned saturating add and subtract
a5660ee x64/reg_alloc: Use type alias for array returned by GetArgumentInfo()
29489b5 ir/value: Use type alias CoprocessorInfo for std::array<u8, 8>
e23ba26 status_register_access: Add support for bits 0 and 1 of mask to MSR
55190bd fuzz_with_unicorn: Split utility functions into fuzz_util
23b049d A32/translate/load_store: Correct detection of writeback
7ec9f15 A32/translate: Add TranslateSingleInstruction
efeecb4 A32/ir_emitter: Bug fix: IREmitter::ExceptionRaised using incorrect opcode
08d1d19 A32/decoders: Split instruction list into include file
2d929cc tests: Refactor unicorn_emu to allow for A32 unicorn
f672368 microinstruction: Improve assert messages
7ebff50 emit_x64_vector: EmitVectorNarrow16: AVX512 implementation
edce230 emit_x64_vector: EmitVectorNarrow32: prefer pblendw to loading constant
2018-08-16 10:12:20 +01:00
bunnei
24a759de4a Merge pull request #1075 from lioncash/include
loader/{nca, xci}: Remove unnecessary includes and unused member variables
2018-08-16 00:04:25 -04:00
Lioncash
b39cd70cd4 common: Namespace hex_util.h/.cpp
It's in the common code, so it should be under the Common namespace like
everything else.
2018-08-15 23:24:00 -04:00
bunnei
c594ec3417 Merge pull request #1005 from DarkLordZach/registered-fmt
file_sys: Add support for registration format
2018-08-15 23:11:58 -04:00
bunnei
89c3d6a2a3 gl_rasterizer_cache: Treat Depth formats differently from DepthStencil. 2018-08-15 21:24:04 -04:00
bunnei
c00b374e78 Merge pull request #1078 from lioncash/message
lm: Handle threads and modules within the logger
2018-08-15 18:53:47 -04:00
bunnei
69236e5aff Merge pull request #1079 from lioncash/fmt
loader: Make ResultStatus directly compatible with fmt
2018-08-15 18:25:57 -04:00
bunnei
1dd27aff47 Merge pull request #1051 from B3n30/UnscheduleEventThreadsafe
Core::CoreTiming: add UnscheduleEventThreadsafe
2018-08-15 18:25:30 -04:00
bunnei
cee6a7ab55 Merge pull request #1080 from lioncash/ret
sm/controller: Correct return value of QueryPointerBufferSize
2018-08-15 18:25:05 -04:00
bunnei
a2fa37b499 Merge pull request #1083 from Subv/conv_neg
Shaders: Implemented I2F_C and F2I_C, along with the negation bits of the conversion instructions.
2018-08-15 18:24:47 -04:00
bunnei
f96de510ee Merge pull request #1081 from lioncash/convert
kernel/server_session: Add IsSession() member function
2018-08-15 13:02:25 -04:00
Subv
91140f6c0a Shader/Conversion: Implemented the negate bit in F2F and I2I instructions. 2018-08-15 09:27:43 -05:00
Subv
38592a3b5e Shader/I2F: Implemented the negate I2F_C instruction variant. 2018-08-15 09:25:02 -05:00
Subv
40ecdda19e Shader/F2I: Implemented the negate bit in the I2F instruction 2018-08-15 09:18:55 -05:00
Subv
5ef447cc0e Shader/F2I: Implemented the F2I_C instruction variant. 2018-08-15 09:16:35 -05:00
Subv
11c221cc62 Shader/F2I: Implemented the negate bit in the F2I instruction. 2018-08-15 09:15:55 -05:00
bunnei
40f83fee6a Merge pull request #1077 from bunnei/rgba16u
gl_rasterizer_cache: Add RGBA16U to PixelFormatFromTextureFormat.
2018-08-15 09:25:15 -04:00
bunnei
0bca5743ab Merge pull request #1076 from bunnei/format-cleanup
gl_rasterizer_cache: Cleanup some PixelFormat names and logging.
2018-08-15 09:24:54 -04:00
Lioncash
aac5792a2b kernel/server_session: Add IsSession() member function
Allows querying the inverse of IsDomain() to make things more readable.
This will likely also be usable in the event of implementing
ConvertDomainToSession().
2018-08-15 06:50:50 -04:00
Lioncash
5752454629 sm/controller: Correct return value of QueryPointerBufferSize
This should be returning a u16 according to Switch Brew.
2018-08-15 06:16:10 -04:00
Lioncash
87d8a9c986 loader: Make ResultStatus directly compatible with fmt
We can make the enum class type compatible with fmt by providing an
overload of operator<<.

While we're at it, perform proper bounds checking. If something exceeds
the array, it should be a hard fail, because it's, without a doubt, a
programmer error in this case.
2018-08-15 05:52:37 -04:00
Lioncash
0769aa594e loader/nca: Remove unnecessary includes and member variables 2018-08-15 01:41:40 -04:00
Lioncash
c8e3f98c27 loader/xci: Remove unnecessary includes and member variables
Many of these aren't necessary and will cause this file to be required
to be recompiled whenever any changes to those files are made, which
lengthens compile times for no reason.

This also removes an unused metadata variable from AppLoader_XCI
2018-08-15 01:41:35 -04:00
Lioncash
b74df62959 lm: Use LOG_DEBUG for printing out trace logs
Using LOG_TRACE here isn't a good idea because LOG_TRACE is only enabled
when yuzu is compiled in debug mode. Debug mode is also quite slow, and
so we're potentially throwing away logging messages that can provide
value when trying to boot games.
2018-08-15 01:07:41 -04:00
Lioncash
e0b0f4eece lm: Handle threads and modules within the logger
The thread field serves to indicate which thread a log is related to and
provides the length of the thread's name, so we can print that out,
ditto for modules.

Now we can know what threads are potentially spawning off logging
messages (for example Lydie & Suelle bounces between MainThread and
LoadingThread when initializing the game).
2018-08-15 01:05:50 -04:00
Subv
c5284efd4f Rasterizer: Implemented instanced rendering.
We keep track of the current instance and update an uniform in the shaders to let them know which instance they are.

Instanced vertex arrays are not yet implemented.
2018-08-14 22:25:07 -05:00
bunnei
8599e1e4fc gl_rasterizer_cache: Add RGBA16U to PixelFormatFromTextureFormat.
- Used by Breath of the Wild.
2018-08-14 23:18:34 -04:00
Lioncash
60f476cd8f common/telemetry: Migrate core-independent info gathering to common
Previously core itself was the library containing the code to gather
common information (build info, CPU info, and OS info), however all of
this isn't core-dependent and can be moved to the common code and use
the common interfaces. We can then just call those functions from the
core instead.

This will allow replacing our CPU detection with Xbyak's which has
better detection facilities than ours. It also keeps more
architecture-dependent code in common instead of core.
2018-08-14 18:57:46 -04:00
B3n30
eab35c8235 Core::CoreTiming: add UnscheduleEventThreadsafe 2018-08-13 13:56:41 +02:00
Zach Hilman
35e4a47be0 registration: Various style and documentation improvements
Fix logic in RealVfsFilesystem Create methods
Remove magic numbers
Fix regex errors
2018-08-12 15:55:44 -04:00
Zach Hilman
6b76b77400 registration: Add support for force overwrite of installed 2018-08-11 23:01:42 -04:00
Zach Hilman
fdf27bf390 game_list: Split game list scans to multiple functions
Avoids unnecessary rebuilds of control data on every layer of recursion in AddFstEntriesToGameList
2018-08-11 22:50:48 -04:00
Zach Hilman
8f06a0f898 vfs_real: Add CreateFullPath to Create* operations 2018-08-11 22:50:48 -04:00
Zach Hilman
dda8ef11c7 control_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman
149bda980a romfs: Remove cyclic shared_ptr leak in romfs code 2018-08-11 22:50:48 -04:00
Zach Hilman
893447b6b0 registration: Update documentation and style 2018-08-11 22:50:48 -04:00
Zach Hilman
22bdddd6f0 nca_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman
62e859c6c7 bis_factory: Create NAND dirs if they don't exist 2018-08-11 22:50:48 -04:00
Zach Hilman
f78a6e752f qt: Use custom RawCopy with progress bar for installs 2018-08-11 22:50:48 -04:00
Zach Hilman
3b3c919e20 registration: Take RawCopy function as parameter
Instead of defaulting to VfsRawCopy
2018-08-11 22:50:48 -04:00
Zach Hilman
10812f8407 game_list: Populate control data from installed NAND 2018-08-11 22:50:48 -04:00
Zach Hilman
e5504a060d registered_cache: Fix missing reading from yuzu_meta 2018-08-11 22:50:48 -04:00
Zach Hilman
167bfddafa file_sys: Comply to style guidelines 2018-08-11 22:50:48 -04:00
Zach Hilman
bfb945c243 qt: Add 'Install to NAND' option to menu
Prompts for title type on NCA files.
2018-08-11 22:50:48 -04:00
Zach Hilman
b67e751ccb game_list: Modify game list to scan installed titles 2018-08-11 22:50:48 -04:00
Zach Hilman
a91983b11c file_sys: Add RegisteredCache
Manages NAND NCA get and install.
2018-08-11 22:50:48 -04:00
Zach Hilman
9aab787122 file_sys: Add support for parsing NCA metadata (CNMT) 2018-08-11 22:50:48 -04:00
Zach Hilman
ab8acce645 card_image: Add accessor for all NCAs in XCI 2018-08-11 22:50:48 -04:00
Zach Hilman
9b0e3556ed vfs_real: Add CreateFullPath to CreateFile
Fixes bugs with calling CreateFile when the immediate directory does not exist.
2018-08-11 22:50:48 -04:00
Zach Hilman
c0257cf52f filesystem: Add Open and Register functions for BISFactory 2018-08-11 22:50:48 -04:00
Zach Hilman
70a510bd8f bis_factory: Add partial implementation of BISFactory
Creates and stores RegisteredCaches for user and system NAND, as creation of a RegisteredCache is expensive.
2018-08-11 22:50:48 -04:00
Zach Hilman
95bb1067c1 loader: Join 0* files in directory if filename is 00
i.e. Load the concatenated 00+01 if 01 exists as well. Needed for split NAND NCAs.
2018-08-11 22:50:48 -04:00
Zach Hilman
5b4119fa7f loader: Recognize filename '00' as NCA
Needed to avoid mismatch filetype warnings on split NAND NCAs
2018-08-11 22:50:08 -04:00
Zach Hilman
42114e1df4 vfs: Add ConcatenatedVfsFile 2018-08-11 22:50:08 -04:00
Zach Hilman
a27ec24c0f crypto: Remove hex utilities from key_manager
Move to hex_util.h in common
2018-08-11 22:50:08 -04:00
Zach Hilman
b70a831608 file_util: Add getter for NAND registration directory 2018-08-11 22:50:08 -04:00
Zach Hilman
10aac376d1 common: Move hex string processing to separate file 2018-08-11 22:50:08 -04:00
David Marcec
10f494eefe Better UUID randomness 2018-08-12 02:31:43 +10:00
David Marcec
448290bee4 Removed un-needed count from ListOpenUsers and ListAllUsers 2018-08-12 02:11:04 +10:00
David Marcec
2592e41301 Added better explanations in the profile manager 2018-08-12 01:51:31 +10:00
David Marcec
0b6f8ba51e Code cleanup for profile manager 2018-08-12 01:34:22 +10:00
David Marcec
d0b2950434 Removed const from ProfileBase Invalidate 2018-08-12 00:41:17 +10:00
David Marcec
42431d2aa6 fixed invalid uuid bool operator 2018-08-11 21:29:10 +10:00
David Marcec
b8e70faa2d Added GetOpenUserCount 2018-08-11 20:45:06 +10:00
David Marcec
662218e997 Removed all for loops from the profile manager 2018-08-11 20:15:59 +10:00
David Marcec
c3013c7c9c Added missing ListAllUsers count 2018-08-11 20:06:06 +10:00
David Marcec
acff922762 If statement style change 2018-08-11 18:46:42 +10:00
David Marcec
dfea525cbe Second round of account changes 2018-08-11 18:26:13 +10:00
David Marcec
82fa0bcea7 First round of account changes 2018-08-11 16:47:33 +10:00
David Marcec
4fa4d80c68 Rebase with dynarmic master 2018-08-11 13:35:04 +10:00
David Marcec
6aa8ee6943 Refactored profile manager sharing 2018-08-11 13:17:06 +10:00
David Marcec
b76ddb7647 Merge remote-tracking branch 'origin/master' into better-account 2018-08-11 10:35:47 +10:00
David Marcec
2a3b335b15 Added IsUserRegistrationRequestPermitted 2018-08-11 10:33:11 +10:00
David Marcec
4e1471ef21 Don't add user if the uuid already exists 2018-08-09 13:30:58 +10:00
David Marcec
e9978fd4f5 Open first user added 2018-08-09 01:37:55 +10:00
David Marcec
75169c7570 Inital pass of account backend implementation
This commit verified working on puyo
2018-08-09 01:09:12 +10:00
David Marcec
03d7faf583 GetProfileBase and GetProfileBaseAndData added 2018-08-08 23:41:12 +10:00
David Marcec
6f691e71bf began initial implementation of "ProfileManager" 2018-08-08 22:26:42 +10:00
David Marcec
5f8d253ce0 Switched uuids from u128 to new UUID struct 2018-08-08 21:09:45 +10:00
fearlessTobi
ed8b2a0254 Port #3616 from Citra 2018-07-26 15:55:23 +02:00
127 changed files with 3820 additions and 745 deletions

2
externals/fmt vendored

View File

@@ -46,7 +46,7 @@ void Filter::Process(std::vector<s16>& signal) {
out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] -
a2 * out[2][ch];
signal[i * 2 + ch] = std::clamp(out[0][ch], -32768.0, 32767.0);
signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0));
}
}
}

View File

@@ -38,6 +38,8 @@ add_library(common STATIC
file_util.cpp
file_util.h
hash.h
hex_util.cpp
hex_util.h
logging/backend.cpp
logging/backend.h
logging/filter.cpp

View File

@@ -178,8 +178,7 @@ public:
return ExtractValue(storage);
}
// TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015
constexpr FORCE_INLINE bool ToBool() const {
constexpr explicit operator bool() const {
return Value() != 0;
}

View File

@@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
#endif
}
std::string GetNANDRegistrationDir(bool system) {
if (system)
return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
}
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
}

View File

@@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
std::string GetHactoolConfigurationPath();
std::string GetNANDRegistrationDir(bool system = false);
// Returns the path to where the sys file are
std::string GetSysDirectory();

31
src/common/hex_util.cpp Normal file
View File

@@ -0,0 +1,31 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/hex_util.h"
namespace Common {
u8 ToHexNibble(char c1) {
if (c1 >= 65 && c1 <= 70)
return c1 - 55;
if (c1 >= 97 && c1 <= 102)
return c1 - 87;
if (c1 >= 48 && c1 <= 57)
return c1 - 48;
throw std::logic_error("Invalid hex digit");
}
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
if (len != 32)
throw std::logic_error("Not of correct size.");
return HexStringToArray<16>(str);
}
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
if (len != 64)
throw std::logic_error("Not of correct size.");
return HexStringToArray<32>(str);
}
} // namespace Common

41
src/common/hex_util.h Normal file
View File

@@ -0,0 +1,41 @@
// 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 <cstddef>
#include <string>
#include <fmt/format.h>
#include "common/common_types.h"
namespace Common {
u8 ToHexNibble(char c1);
template <size_t Size, bool le = false>
std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
if constexpr (le) {
for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
} else {
for (size_t i = 0; i < 2 * Size; i += 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
}
return out;
}
template <size_t Size>
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;
for (u8 c : array)
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
return out;
}
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
} // namespace Common

View File

@@ -42,7 +42,7 @@ void PrintColoredMessage(const Entry& entry) {
return;
}
CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
CONSOLE_SCREEN_BUFFER_INFO original_info = {};
GetConsoleScreenBufferInfo(console_handle, &original_info);
WORD color = 0;

View File

@@ -3,8 +3,15 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include "common/assert.h"
#include "common/scm_rev.h"
#include "common/telemetry.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#endif
namespace Telemetry {
void FieldCollection::Accept(VisitorInterface& visitor) const {
@@ -37,4 +44,62 @@ template class Field<std::string>;
template class Field<const char*>;
template class Field<std::chrono::microseconds>;
#ifdef ARCHITECTURE_x86_64
static const char* CpuVendorToStr(Common::CPUVendor vendor) {
switch (vendor) {
case Common::CPUVendor::INTEL:
return "Intel";
case Common::CPUVendor::AMD:
return "Amd";
case Common::CPUVendor::OTHER:
return "Other";
}
UNREACHABLE();
}
#endif
void AppendBuildInfo(FieldCollection& fc) {
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty);
fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch);
fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev);
fc.AddField(FieldType::App, "BuildDate", Common::g_build_date);
fc.AddField(FieldType::App, "BuildName", Common::g_build_name);
}
void AppendCPUInfo(FieldCollection& fc) {
#ifdef ARCHITECTURE_x86_64
fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string);
fc.AddField(FieldType::UserSystem, "CPU_Vendor", CpuVendorToStr(Common::GetCPUCaps().vendor));
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSSE3", Common::GetCPUCaps().ssse3);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE41", Common::GetCPUCaps().sse4_1);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2);
#else
fc.AddField(FieldType::UserSystem, "CPU_Model", "Other");
#endif
}
void AppendOSInfo(FieldCollection& fc) {
#ifdef __APPLE__
fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple");
#elif defined(_WIN32)
fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows");
#elif defined(__linux__) || defined(linux) || defined(__linux)
fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux");
#else
fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown");
#endif
}
} // namespace Telemetry

View File

@@ -180,4 +180,16 @@ struct NullVisitor : public VisitorInterface {
void Complete() override {}
};
/// Appends build-specific information to the given FieldCollection,
/// such as branch name, revision hash, etc.
void AppendBuildInfo(FieldCollection& fc);
/// Appends CPU-specific information to the given FieldCollection,
/// such as instruction set extensions, etc.
void AppendCPUInfo(FieldCollection& fc);
/// Appends OS-specific information to the given FieldCollection,
/// such as platform name, etc.
void AppendOSInfo(FieldCollection& fc);
} // namespace Telemetry

View File

@@ -20,6 +20,8 @@ add_library(core STATIC
crypto/key_manager.h
crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
file_sys/content_archive.cpp
@@ -29,10 +31,14 @@ add_library(core STATIC
file_sys/directory.h
file_sys/errors.h
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h
file_sys/program_metadata.cpp
file_sys/program_metadata.h
file_sys/registered_cache.cpp
file_sys/registered_cache.h
file_sys/romfs.cpp
file_sys/romfs.h
file_sys/romfs_factory.cpp
@@ -43,6 +49,8 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
file_sys/vfs_concat.h
file_sys/vfs_offset.cpp
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
@@ -114,6 +122,8 @@ add_library(core STATIC
hle/service/acc/acc_u0.h
hle/service/acc/acc_u1.cpp
hle/service/acc/acc_u1.h
hle/service/acc/profile_manager.cpp
hle/service/acc/profile_manager.h
hle/service/am/am.cpp
hle/service/am/am.h
hle/service/am/applet_ae.cpp

View File

@@ -134,6 +134,9 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
config.dczid_el0 = 4;
config.ctr_el0 = 0x8444c004;
// Unpredictable instructions
config.define_unpredictable_behaviour = true;
return std::make_unique<Dynarmic::A64::Jit>(config);
}

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/gdbstub/gdbstub.h"
@@ -17,6 +18,7 @@
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/settings.h"
#include "file_sys/vfs_concat.h"
#include "file_sys/vfs_real.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
return RunLoop(false);
}
static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path) {
// To account for split 00+01+etc files.
std::string dir_name;
std::string filename;
Common::SplitPath(path, &dir_name, &filename, nullptr);
if (filename == "00") {
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
std::vector<FileSys::VirtualFile> concat;
for (u8 i = 0; i < 0x10; ++i) {
auto next = dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else {
next = dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else
break;
}
}
if (concat.empty())
return nullptr;
return FileSys::ConcatenateFiles(concat, dir->GetName());
}
return vfs->OpenFile(path, FileSys::Mode::Read);
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);

View File

@@ -40,6 +40,12 @@ namespace Core {
class System {
public:
System(const System&) = delete;
System& operator=(const System&) = delete;
System(System&&) = delete;
System& operator=(System&&) = delete;
~System();
/**

View File

@@ -56,6 +56,9 @@ static u64 event_fifo_id;
// to the event_queue by the emu thread
static Common::MPSCQueue<Event, false> ts_queue;
// the queue for unscheduling the events from other threads threadsafe
static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue;
constexpr int MAX_SLICE_LENGTH = 20000;
static s64 idled_cycles;
@@ -158,6 +161,10 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {
}
}
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
unschedule_queue.Push(std::make_pair(event_type, userdata));
}
void RemoveEvent(const EventType* event_type) {
auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
@@ -194,6 +201,9 @@ void MoveEvents() {
void Advance() {
MoveEvents();
for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
UnscheduleEvent(ev.first, ev.second);
}
int cycles_executed = slice_length - downcount;
global_timer += cycles_executed;

View File

@@ -65,6 +65,7 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata);
void UnscheduleEvent(const EventType* event_type, u64 userdata);
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const EventType* event_type);

View File

@@ -10,44 +10,13 @@
#include <string_view>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/key_manager.h"
#include "core/settings.h"
namespace Core::Crypto {
static u8 ToHexNibble(char c1) {
if (c1 >= 65 && c1 <= 70)
return c1 - 55;
if (c1 >= 97 && c1 <= 102)
return c1 - 87;
if (c1 >= 48 && c1 <= 57)
return c1 - 48;
throw std::logic_error("Invalid hex digit");
}
template <size_t Size>
static std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
for (size_t i = 0; i < 2 * Size; i += 2) {
auto d1 = str[i];
auto d2 = str[i + 1];
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
}
return out;
}
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
if (len != 32)
throw std::logic_error("Not of correct size.");
return HexStringToArray<16>(str);
}
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
if (len != 64)
throw std::logic_error("Not of correct size.");
return HexStringToArray<32>(str);
}
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -83,20 +52,20 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
if (is_title_keys) {
auto rights_id_raw = HexStringToArray<16>(out[0]);
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
u128 rights_id{};
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
Key128 key = HexStringToArray<16>(out[1]);
Key128 key = Common::HexStringToArray<16>(out[1]);
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
} else {
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
if (s128_file_id.find(out[0]) != s128_file_id.end()) {
const auto index = s128_file_id.at(out[0]);
Key128 key = HexStringToArray<16>(out[1]);
Key128 key = Common::HexStringToArray<16>(out[1]);
SetKey(index.type, key, index.field1, index.field2);
} else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
const auto index = s256_file_id.at(out[0]);
Key256 key = HexStringToArray<32>(out[1]);
Key256 key = Common::HexStringToArray<32>(out[1]);
SetKey(index.type, key, index.field1, index.field2);
}
}

View File

@@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
namespace Core::Crypto {
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
class KeyManager {
public:
KeyManager();

View File

@@ -0,0 +1,31 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/file_sys/bis_factory.h"
namespace FileSys {
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
const auto res = dir->GetDirectoryRelative(path);
if (res == nullptr)
return dir->CreateDirectoryRelative(path);
return res;
}
BISFactory::BISFactory(VirtualDir nand_root_)
: nand_root(std::move(nand_root_)),
sysnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
return sysnand_cache;
}
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
return usrnand_cache;
}
} // namespace FileSys

View File

@@ -0,0 +1,30 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/loader/loader.h"
#include "registered_cache.h"
namespace FileSys {
/// File system interface to the Built-In Storage
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
/// registered caches.
class BISFactory {
public:
explicit BISFactory(VirtualDir nand_root);
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
private:
VirtualDir nand_root;
std::shared_ptr<RegisteredCache> sysnand_cache;
std::shared_ptr<RegisteredCache> usrnand_cache;
};
} // namespace FileSys

View File

@@ -4,11 +4,14 @@
#include <array>
#include <string>
#include <core/loader/loader.h>
#include <fmt/ostream.h>
#include "common/logging/log.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
namespace FileSys {
@@ -93,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {
return GetPartition(XCIPartition::Logo);
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
return ncas;
}
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
const auto iter =
std::find_if(ncas.begin(), ncas.end(),
@@ -142,7 +149,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
const u16 error_id = static_cast<u16>(nca->GetStatus());
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
partition_names[static_cast<size_t>(part)], nca->GetName(), error_id,
Loader::GetMessageForResultStatus(nca->GetStatus()));
nca->GetStatus());
}
}

View File

@@ -68,6 +68,7 @@ public:
VirtualDir GetUpdatePartition() const;
VirtualDir GetLogoPartition() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
VirtualFile GetNCAFileByType(NCAContentType type) const;

View File

@@ -27,6 +27,7 @@ enum class NCAContentType : u8 {
Control = 2,
Manual = 3,
Data = 4,
Data_Unknown5 = 5, ///< Seems to be used on some system archives
};
enum class NCASectionCryptoType : u8 {

View File

@@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
}
NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
file->ReadObject(raw.get());
}

View File

@@ -81,7 +81,6 @@ public:
std::string GetVersionString() const;
private:
VirtualFile file;
std::unique_ptr<RawNACP> raw;
};

View File

@@ -0,0 +1,131 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "content_archive.h"
#include "core/file_sys/nca_metadata.h"
namespace FileSys {
bool operator>=(TitleType lhs, TitleType rhs) {
return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
}
bool operator<=(TitleType lhs, TitleType rhs) {
return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
}
CNMT::CNMT(VirtualFile file) {
if (file->ReadObject(&header) != sizeof(CNMTHeader))
return;
// If type is {Application, Update, AOC} has opt-header.
if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
LOG_WARNING(Loader, "Failed to read optional header.");
}
}
for (u16 i = 0; i < header.number_content_entries; ++i) {
auto& next = content_records.emplace_back(ContentRecord{});
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
header.table_offset) != sizeof(ContentRecord)) {
content_records.erase(content_records.end() - 1);
}
}
for (u16 i = 0; i < header.number_meta_entries; ++i) {
auto& next = meta_records.emplace_back(MetaRecord{});
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
header.table_offset) != sizeof(MetaRecord)) {
meta_records.erase(meta_records.end() - 1);
}
}
}
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records)
: header(std::move(header)), opt_header(std::move(opt_header)),
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
u64 CNMT::GetTitleID() const {
return header.title_id;
}
u32 CNMT::GetTitleVersion() const {
return header.title_version;
}
TitleType CNMT::GetType() const {
return header.type;
}
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
return content_records;
}
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
return meta_records;
}
bool CNMT::UnionRecords(const CNMT& other) {
bool change = false;
for (const auto& rec : other.content_records) {
const auto iter = std::find_if(content_records.begin(), content_records.end(),
[&rec](const ContentRecord& r) {
return r.nca_id == rec.nca_id && r.type == rec.type;
});
if (iter == content_records.end()) {
content_records.emplace_back(rec);
++header.number_content_entries;
change = true;
}
}
for (const auto& rec : other.meta_records) {
const auto iter =
std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
return r.title_id == rec.title_id && r.title_version == rec.title_version &&
r.type == rec.type;
});
if (iter == meta_records.end()) {
meta_records.emplace_back(rec);
++header.number_meta_entries;
change = true;
}
}
return change;
}
std::vector<u8> CNMT::Serialize() const {
const bool has_opt_header =
header.type >= TitleType::Application && header.type <= TitleType::AOC;
const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
std::vector<u8> out(
std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
memcpy(out.data(), &header, sizeof(CNMTHeader));
// Optional Header
if (has_opt_header) {
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
}
auto offset = header.table_offset;
for (const auto& rec : content_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
offset += sizeof(ContentRecord);
}
for (const auto& rec : meta_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
offset += sizeof(MetaRecord);
}
return out;
}
} // namespace FileSys

View File

@@ -0,0 +1,112 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstring>
#include <memory>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class CNMT;
struct CNMTHeader;
struct OptionalHeader;
enum class TitleType : u8 {
SystemProgram = 0x01,
SystemDataArchive = 0x02,
SystemUpdate = 0x03,
FirmwarePackageA = 0x04,
FirmwarePackageB = 0x05,
Application = 0x80,
Update = 0x81,
AOC = 0x82,
DeltaTitle = 0x83,
};
bool operator>=(TitleType lhs, TitleType rhs);
bool operator<=(TitleType lhs, TitleType rhs);
enum class ContentRecordType : u8 {
Meta = 0,
Program = 1,
Data = 2,
Control = 3,
Manual = 4,
Legal = 5,
Patch = 6,
};
struct ContentRecord {
std::array<u8, 0x20> hash;
std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size;
ContentRecordType type;
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
struct MetaRecord {
u64_le title_id;
u32_le title_version;
TitleType type;
u8 install_byte;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
struct OptionalHeader {
u64_le title_id;
u64_le minimum_version;
};
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
struct CNMTHeader {
u64_le title_id;
u32_le title_version;
TitleType type;
INSERT_PADDING_BYTES(1);
u16_le table_offset;
u16_le number_content_entries;
u16_le number_meta_entries;
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
class CNMT {
public:
explicit CNMT(VirtualFile file);
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records);
u64 GetTitleID() const;
u32 GetTitleVersion() const;
TitleType GetType() const;
const std::vector<ContentRecord>& GetContentRecords() const;
const std::vector<MetaRecord>& GetMetaRecords() const;
bool UnionRecords(const CNMT& other);
std::vector<u8> Serialize() const;
private:
CNMTHeader header;
OptionalHeader opt_header;
std::vector<ContentRecord> content_records;
std::vector<MetaRecord> meta_records;
// TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
// after the table. This is not documented, unfortunately.
};
} // namespace FileSys

View File

@@ -0,0 +1,479 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/encryption_layer.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_concat.h"
namespace FileSys {
std::string RegisteredCacheEntry::DebugInfo() const {
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
}
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
static bool FollowsTwoDigitDirFormat(std::string_view name) {
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
std::regex_constants::icase);
return std::regex_match(name.begin(), name.end(), two_digit_regex);
}
static bool FollowsNcaIdFormat(std::string_view name) {
static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
std::regex_constants::icase);
return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
}
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
bool within_two_digit) {
if (!within_two_digit)
return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper));
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
return fmt::format("/000000{:02X}/{}.nca", hash[0],
Common::HexArrayToString(nca_id, second_hex_upper));
}
static std::string GetCNMTName(TitleType type, u64 title_id) {
constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
"SystemProgram",
"SystemData",
"SystemUpdate",
"BootImagePackage",
"BootImagePackageSafe",
"Application",
"Patch",
"AddOnContent",
"" ///< Currently unknown 'DeltaTitle'
};
auto index = static_cast<size_t>(type);
// If the index is after the jump in TitleType, subtract it out.
if (index >= static_cast<size_t>(TitleType::Application)) {
index -= static_cast<size_t>(TitleType::Application) -
static_cast<size_t>(TitleType::FirmwarePackageB);
}
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
// TODO(DarkLordZach): Differentiate between Program and Patch
return ContentRecordType::Program;
case NCAContentType::Meta:
return ContentRecordType::Meta;
case NCAContentType::Control:
return ContentRecordType::Control;
case NCAContentType::Data:
case NCAContentType::Data_Unknown5:
return ContentRecordType::Data;
case NCAContentType::Manual:
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
return ContentRecordType::Manual;
default:
UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type));
}
}
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
if (dir->GetFileRelative(path) != nullptr)
return dir->GetFileRelative(path);
if (dir->GetDirectoryRelative(path) != nullptr) {
const auto nca_dir = dir->GetDirectoryRelative(path);
VirtualFile file = nullptr;
const auto files = nca_dir->GetFiles();
if (files.size() == 1 && files[0]->GetName() == "00") {
file = files[0];
} else {
std::vector<VirtualFile> concat;
// Since the files are a two-digit hex number, max is FF.
for (size_t i = 0; i < 0x100; ++i) {
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr) {
concat.push_back(std::move(next));
} else {
next = nca_dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else
break;
}
}
if (concat.empty())
return nullptr;
file = FileSys::ConcatenateFiles(concat);
}
return file;
}
return nullptr;
}
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
VirtualFile file;
// Try all four modes of file storage:
// (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
// 00: /000000**/{:032X}.nca
// 01: /{:032X}.nca
// 10: /000000**/{:032x}.nca
// 11: /{:032x}.nca
for (u8 i = 0; i < 4; ++i) {
const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
file = OpenFileOrDirectoryConcat(dir, path);
if (file != nullptr)
return file;
}
return file;
}
static boost::optional<NcaID> CheckMapForContentRecord(
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
if (map.find(title_id) == map.end())
return boost::none;
const auto& cnmt = map.at(title_id);
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (iter == cnmt.GetContentRecords().end())
return boost::none;
return boost::make_optional(iter->nca_id);
}
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
ContentRecordType type) const {
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
return meta_id.at(title_id);
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
if (res1 != boost::none)
return res1;
return CheckMapForContentRecord(meta, title_id, type);
}
std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
std::vector<NcaID> ids;
for (const auto& d2_dir : dir->GetSubdirectories()) {
if (FollowsNcaIdFormat(d2_dir->GetName())) {
ids.push_back(Common::HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
continue;
}
if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
continue;
for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
if (!FollowsNcaIdFormat(nca_dir->GetName()))
continue;
ids.push_back(Common::HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
}
for (const auto& nca_file : d2_dir->GetFiles()) {
if (!FollowsNcaIdFormat(nca_file->GetName()))
continue;
ids.push_back(
Common::HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
}
}
for (const auto& d2_file : dir->GetFiles()) {
if (FollowsNcaIdFormat(d2_file->GetName()))
ids.push_back(Common::HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
}
return ids;
}
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
for (const auto& id : ids) {
const auto file = GetFileAtID(id);
if (file == nullptr)
continue;
const auto nca = std::make_shared<NCA>(parser(file, id));
if (nca->GetStatus() != Loader::ResultStatus::Success ||
nca->GetType() != NCAContentType::Meta) {
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& file : section0->GetFiles()) {
if (file->GetExtension() != "cnmt")
continue;
meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
meta_id.insert_or_assign(nca->GetTitleId(), id);
break;
}
}
}
void RegisteredCache::AccumulateYuzuMeta() {
const auto dir = this->dir->GetSubdirectory("yuzu_meta");
if (dir == nullptr)
return;
for (const auto& file : dir->GetFiles()) {
if (file->GetExtension() != "cnmt")
continue;
CNMT cnmt(file);
yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
}
}
void RegisteredCache::Refresh() {
if (dir == nullptr)
return;
const auto ids = AccumulateFiles();
ProcessFiles(ids);
AccumulateYuzuMeta();
}
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
Refresh();
}
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr;
}
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry) != nullptr;
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
if (id == boost::none)
return nullptr;
return parser(GetFileAtID(id.get()), id.get());
}
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry.title_id, entry.type);
}
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
return std::make_shared<NCA>(raw);
}
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
template <typename T>
void RegisteredCache::IterateAllMetadata(
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
for (const auto& kv : meta) {
const auto& cnmt = kv.second;
if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
for (const auto& rec : cnmt.GetContentRecords()) {
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
out.push_back(proc(cnmt, rec));
}
}
}
for (const auto& kv : yuzu_meta) {
const auto& cnmt = kv.second;
for (const auto& rec : cnmt.GetContentRecords()) {
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
out.push_back(proc(cnmt, rec));
}
}
}
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
std::vector<RegisteredCacheEntry> out;
IterateAllMetadata<RegisteredCacheEntry>(
out,
[](const CNMT& c, const ContentRecord& r) {
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[](const CNMT& c, const ContentRecord& r) { return true; });
return out;
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
boost::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
IterateAllMetadata<RegisteredCacheEntry>(
out,
[](const CNMT& c, const ContentRecord& r) {
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
if (title_type != boost::none && title_type.get() != c.GetType())
return false;
if (record_type != boost::none && record_type.get() != r.type)
return false;
if (title_id != boost::none && title_id.get() != c.GetTitleID())
return false;
return true;
});
return out;
}
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false));
const auto iter =
std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
[&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
return iter == xci->GetNCAs().end() ? nullptr : *iter;
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
const auto& ncas = xci->GetNCAs();
const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
return nca->GetType() == NCAContentType::Meta;
});
if (meta_iter == ncas.end()) {
LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
"is therefore malformed. Double check your encryption keys.");
return InstallResult::ErrorMetaFailed;
}
// Install Metadata File
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
const auto meta_id = Common::HexStringToArray<16>(meta_id_raw);
const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
if (res != InstallResult::Success)
return res;
// Install all the other NCAs
const auto section0 = (*meta_iter)->GetSubdirectories()[0];
const auto cnmt_file = section0->GetFiles()[0];
const CNMT cnmt(cnmt_file);
for (const auto& record : cnmt.GetContentRecords()) {
const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
if (nca == nullptr)
return InstallResult::ErrorCopyFailed;
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
if (res2 != InstallResult::Success)
return res2;
}
Refresh();
return InstallResult::Success;
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
CNMTHeader header{
nca->GetTitleId(), ///< Title ID
0, ///< Ignore/Default title version
type, ///< Type
{}, ///< Padding
0x10, ///< Default table offset
1, ///< 1 Content Entry
0, ///< No Meta Entries
{}, ///< Padding
};
OptionalHeader opt_header{0, 0};
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
if (!RawInstallYuzuMeta(new_cnmt))
return InstallResult::ErrorMetaFailed;
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
boost::optional<NcaID> override_id) {
const auto in = nca->GetBaseFile();
Core::Crypto::SHA256Hash hash{};
// Calculate NcaID
// NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
// game is massive), we're going to cheat and only hash the first MB of the NCA.
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
NcaID id{};
if (override_id == boost::none) {
const auto& data = in->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
} else {
id = override_id.get();
}
std::string path = GetRelativePathFromNcaID(id, false, true);
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
return InstallResult::ErrorAlreadyExists;
}
if (GetFileAtID(id) != nullptr) {
LOG_WARNING(Loader, "Overwriting existing NCA...");
VirtualDir c_dir;
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
c_dir->DeleteFile(FileUtil::GetFilename(path));
}
auto out = dir->CreateFileRelative(path);
if (out == nullptr)
return InstallResult::ErrorCopyFailed;
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
}
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
// Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
if (dir->GetFile(filename) == nullptr) {
auto out = dir->CreateFile(filename);
const auto buffer = cnmt.Serialize();
out->Resize(buffer.size());
out->WriteBytes(buffer);
} else {
auto out = dir->GetFile(filename);
CNMT old_cnmt(out);
// Returns true on change
if (old_cnmt.UnionRecords(cnmt)) {
out->Resize(0);
const auto buffer = old_cnmt.Serialize();
out->Resize(buffer.size());
out->WriteBytes(buffer);
}
}
Refresh();
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
[&cnmt](const std::pair<u64, CNMT>& kv) {
return kv.second.GetType() == cnmt.GetType() &&
kv.second.GetTitleID() == cnmt.GetTitleID();
}) != yuzu_meta.end();
}
} // namespace FileSys

View File

@@ -0,0 +1,124 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/flat_map.hpp>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class XCI;
class CNMT;
using NcaID = std::array<u8, 0x10>;
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
enum class InstallResult {
Success,
ErrorAlreadyExists,
ErrorCopyFailed,
ErrorMetaFailed,
};
struct RegisteredCacheEntry {
u64 title_id;
ContentRecordType type;
std::string DebugInfo() const;
};
// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
/*
* A class that catalogues NCAs in the registered directory structure.
* Nintendo's registered format follows this structure:
*
* Root
* | 000000XX <- XX is the ____ two digits of the NcaID
* | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
* | 00
* | 01 <- Actual content split along 4GB boundaries. (optional)
*
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
* 4GB splitting can be ignored.)
*/
class RegisteredCache {
public:
// Parsing function defines the conversion from raw file to NCA. If there are other steps
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
// parsing function.
explicit RegisteredCache(VirtualDir dir,
RegisteredCacheParsingFunction parsing_function =
[](const VirtualFile& file, const NcaID& id) { return file; });
void Refresh();
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
// If a parameter is not boost::none, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
boost::optional<TitleType> title_type = boost::none,
boost::optional<ContentRecordType> record_type = boost::none,
boost::optional<u64> title_id = boost::none) const;
// Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
// is a meta NCA and all of them are accessible.
InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
// Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
// poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
// dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
private:
template <typename T>
void IterateAllMetadata(std::vector<T>& out,
std::function<T(const CNMT&, const ContentRecord&)> proc,
std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateYuzuMeta();
boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
boost::optional<NcaID> override_id = boost::none);
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
RegisteredCacheParsingFunction parser;
// maps tid -> NcaID of meta
boost::container::flat_map<u64, NcaID> meta_id;
// maps tid -> meta
boost::container::flat_map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta
boost::container::flat_map<u64, CNMT> yuzu_meta;
};
} // namespace FileSys

View File

@@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
parent->AddFile(std::make_shared<OffsetVfsFile>(
file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
file, entry.first.size, entry.first.offset + data_offset, entry.second));
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
break;
@@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
while (true) {
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
auto current = std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
@@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
const u64 file_offset = header.file_meta.offset;
const u64 dir_offset = header.directory_meta.offset + 4;
const auto root =
auto root =
std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
file->GetContainingDirectory(), file->GetName());
file->GetName(), file->GetContainingDirectory());
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);

View File

@@ -6,20 +6,57 @@
#include <memory>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
namespace FileSys {
RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
// Load the RomFS from the app
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(file)) {
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
LOG_ERROR(Service_FS, "Unable to read RomFS!");
}
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) {
// TODO(DarkLordZach): Use title id.
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
return MakeResult<VirtualFile>(file);
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
switch (storage) {
case StorageId::NandSystem: {
const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
return MakeResult<VirtualFile>(romfs);
}
case StorageId::NandUser: {
const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
return MakeResult<VirtualFile>(romfs);
}
default:
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
}
}
} // namespace FileSys

View File

@@ -6,17 +6,33 @@
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
#include "core/hle/result.h"
#include "core/loader/loader.h"
namespace Loader {
class AppLoader;
} // namespace Loader
namespace FileSys {
enum class ContentRecordType : u8;
enum class StorageId : u8 {
None = 0,
Host = 1,
GameCard = 2,
NandSystem = 3,
NandUser = 4,
SdCard = 5,
};
/// File system interface to the RomFS archive
class RomFSFactory {
public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
ResultVal<VirtualFile> Open(u64 title_id);
ResultVal<VirtualFile> OpenCurrentProcess();
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
private:
VirtualFile file;

View File

@@ -73,7 +73,7 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
}
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id) const {
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData && title_id == 0)

View File

@@ -49,11 +49,11 @@ public:
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id);
private:
VirtualDir dir;
std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
u64 save_id) const;
};
} // namespace FileSys

View File

@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <memory>
#include "core/core.h"
#include "core/file_sys/sdmc_factory.h"
namespace FileSys {

View File

@@ -4,6 +4,7 @@
#pragma once
#include "core/file_sys/vfs.h"
#include "core/hle/result.h"
namespace FileSys {

View File

@@ -8,6 +8,7 @@
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/vfs.h"
namespace FileSys {

View File

@@ -9,9 +9,8 @@
#include <string_view>
#include <type_traits>
#include <vector>
#include "boost/optional.hpp"
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "core/file_sys/mode.h"
namespace FileSys {
@@ -19,6 +18,8 @@ class VfsDirectory;
class VfsFile;
class VfsFilesystem;
enum class Mode : u32;
// Convenience typedefs to use Vfs* interfaces
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
using VirtualDir = std::shared_ptr<VfsDirectory>;

View File

@@ -0,0 +1,94 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <utility>
#include "core/file_sys/vfs_concat.h"
namespace FileSys {
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
if (files.empty())
return nullptr;
if (files.size() == 1)
return files[0];
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
: name(std::move(name)) {
size_t next_offset = 0;
for (const auto& file : files_) {
files[next_offset] = file;
next_offset += file->GetSize();
}
}
std::string ConcatenatedVfsFile::GetName() const {
if (files.empty())
return "";
if (!name.empty())
return name;
return files.begin()->second->GetName();
}
size_t ConcatenatedVfsFile::GetSize() const {
if (files.empty())
return 0;
return files.rbegin()->first + files.rbegin()->second->GetSize();
}
bool ConcatenatedVfsFile::Resize(size_t new_size) {
return false;
}
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
if (files.empty())
return nullptr;
return files.begin()->second->GetContainingDirectory();
}
bool ConcatenatedVfsFile::IsWritable() const {
return false;
}
bool ConcatenatedVfsFile::IsReadable() const {
return true;
}
size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
auto entry = files.end();
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if (iter->first > offset) {
entry = --iter;
break;
}
}
// Check if the entry should be the last one. The loop above will make it end().
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
--entry;
if (entry == files.end())
return 0;
const auto remaining = entry->second->GetSize() + offset - entry->first;
if (length > remaining) {
return entry->second->Read(data, remaining, offset - entry->first) +
Read(data + remaining, length - remaining, offset + remaining);
}
return entry->second->Read(data, length, offset - entry->first);
}
size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
return 0;
}
bool ConcatenatedVfsFile::Rename(std::string_view name) {
return false;
}
} // namespace FileSys

View File

@@ -0,0 +1,41 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string_view>
#include <boost/container/flat_map.hpp>
#include "core/file_sys/vfs.h"
namespace FileSys {
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
// read-only.
class ConcatenatedVfsFile : public VfsFile {
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
public:
std::string GetName() const override;
size_t GetSize() const override;
bool Resize(size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
size_t Read(u8* data, size_t length, size_t offset) const override;
size_t Write(const u8* data, size_t length, size_t offset) override;
bool Rename(std::string_view name) override;
private:
// Maps starting offset to file -- more efficient.
boost::container::flat_map<u64, VirtualFile> files;
std::string name;
};
} // namespace FileSys

View File

@@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
return nullptr;
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
if (!FileUtil::Exists(path)) {
FileUtil::CreateFullPath(path_fwd);
if (!FileUtil::CreateEmptyFile(path))
return nullptr;
}
return OpenFile(path, perms);
}
@@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
return nullptr;
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
if (!FileUtil::Exists(path)) {
FileUtil::CreateFullPath(path_fwd);
if (!FileUtil::CreateDir(path))
return nullptr;
}
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
}
@@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FileUtil::Exists(full_path))
if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
return nullptr;
return base.OpenFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FileUtil::Exists(full_path))
if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
return nullptr;
return base.OpenDirectory(full_path, perms);
}

View File

@@ -5,7 +5,6 @@
#pragma once
#include <string_view>
#include <boost/container/flat_map.hpp>
#include "common/file_util.h"
#include "core/file_sys/mode.h"

View File

@@ -8,8 +8,8 @@
namespace FileSys {
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
std::vector<VirtualDir> dirs_, VirtualDir parent_,
std::string name_)
std::vector<VirtualDir> dirs_, std::string name_,
VirtualDir parent_)
: files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
name(std::move(name_)) {}

View File

@@ -13,8 +13,8 @@ namespace FileSys {
class VectorVfsDirectory : public VfsDirectory {
public:
explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
std::string name = "");
std::vector<VirtualDir> dirs = {}, std::string name = "",
VirtualDir parent = nullptr);
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;

View File

@@ -152,7 +152,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
// end of the command such that only commands following this one are handled as domains
if (convert_to_domain) {
ASSERT_MSG(domain_request_handlers.empty(), "already a domain");
ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
domain_request_handlers = {hle_handler};
convert_to_domain = false;
}

View File

@@ -97,7 +97,12 @@ public:
/// Returns true if the session has been converted to a domain, otherwise False
bool IsDomain() const {
return !domain_request_handlers.empty();
return !IsSession();
}
/// Returns true if this session has not been converted to a domain, otherwise false.
bool IsSession() const {
return domain_request_handlers.empty();
}
/// Converts the session to a domain at the end of the current command

View File

@@ -167,7 +167,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
}
void Thread::CancelWakeupTimer() {
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle);
}
static boost::optional<s32> GetNextProcessorId(u64 mask) {

View File

@@ -3,17 +3,19 @@
// Refer to the license.txt file included.
#include <array>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/acc/acc_aa.h"
#include "core/hle/service/acc/acc_su.h"
#include "core/hle/service/acc/acc_u0.h"
#include "core/hle/service/acc/acc_u1.h"
#include "core/settings.h"
#include "core/hle/service/acc/profile_manager.h"
namespace Service::Account {
// TODO: RE this structure
struct UserData {
INSERT_PADDING_WORDS(1);
@@ -25,19 +27,10 @@ struct UserData {
};
static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
struct ProfileBase {
u128 user_id;
u64 timestamp;
std::array<u8, 0x20> username;
};
static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size");
// TODO(ogniK): Generate a real user id based on username, md5(username) maybe?
static constexpr u128 DEFAULT_USER_ID{1ull, 0ull};
class IProfile final : public ServiceFramework<IProfile> {
public:
explicit IProfile(u128 user_id) : ServiceFramework("IProfile"), user_id(user_id) {
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
: ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) {
static const FunctionInfo functions[] = {
{0, &IProfile::Get, "Get"},
{1, &IProfile::GetBase, "GetBase"},
@@ -49,46 +42,42 @@ public:
private:
void Get(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
profile_base.user_id = user_id;
if (Settings::values.username.size() > profile_base.username.size()) {
std::copy_n(Settings::values.username.begin(), profile_base.username.size(),
profile_base.username.begin());
std::array<u8, MAX_DATA> data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
} else {
std::copy(Settings::values.username.begin(), Settings::values.username.end(),
profile_base.username.begin());
LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}",
user_id.Format());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code
}
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
}
void GetBase(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// TODO(Subv): Retrieve this information from somewhere.
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
profile_base.user_id = user_id;
if (Settings::values.username.size() > profile_base.username.size()) {
std::copy_n(Settings::values.username.begin(), profile_base.username.size(),
profile_base.username.begin());
if (profile_manager.GetProfileBase(user_id, profile_base)) {
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
} else {
std::copy(Settings::values.username.begin(), Settings::values.username.end(),
profile_base.username.begin());
LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code
}
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
}
void LoadImage(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
// TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000
const u32 jpeg_size = 107;
static const std::array<u8, jpeg_size> jpeg{
constexpr u32 jpeg_size = 107;
static constexpr std::array<u8, jpeg_size> jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03,
0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04,
0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a,
@@ -98,13 +87,14 @@ private:
0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
ctx.WriteBuffer(jpeg.data(), jpeg_size);
ctx.WriteBuffer(jpeg);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(jpeg_size);
}
u128 user_id; ///< The user id this profile refers to.
const ProfileManager& profile_manager;
UUID user_id; ///< The user id this profile refers to.
};
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
@@ -141,44 +131,57 @@ private:
};
void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
LOG_INFO(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1);
rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount()));
}
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::RequestParser rp{ctx};
UUID user_id = rp.PopRaw<UUID>();
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(true); // TODO: Check when this is supposed to return true and when not
rb.Push(profile_manager->UserExists(user_id));
}
void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// TODO(Subv): There is only one user for now.
const std::vector<u128> user_ids = {DEFAULT_USER_ID};
ctx.WriteBuffer(user_ids);
LOG_INFO(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetAllUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// TODO(Subv): There is only one user for now.
const std::vector<u128> user_ids = {DEFAULT_USER_ID};
ctx.WriteBuffer(user_ids);
LOG_INFO(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetOpenUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser());
}
void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u128 user_id = rp.PopRaw<u128>();
UUID user_id = rp.PopRaw<UUID>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IProfile>(user_id);
LOG_DEBUG(Service_ACC, "called user_id=0x{:016X}{:016X}", user_id[1], user_id[0]);
rb.PushIpcInterface<IProfile>(user_id, *profile_manager);
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
}
void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(profile_manager->CanSystemRegisterUser());
}
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
@@ -194,22 +197,20 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
LOG_DEBUG(Service_ACC, "called");
}
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(DEFAULT_USER_ID);
}
Module::Interface::Interface(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager, const char* name)
: ServiceFramework(name), module(std::move(module)),
profile_manager(std::move(profile_manager)) {}
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)) {}
Module::Interface::~Interface() = default;
void InstallInterfaces(SM::ServiceManager& service_manager) {
auto module = std::make_shared<Module>();
std::make_shared<ACC_AA>(module)->InstallAsService(service_manager);
std::make_shared<ACC_SU>(module)->InstallAsService(service_manager);
std::make_shared<ACC_U0>(module)->InstallAsService(service_manager);
std::make_shared<ACC_U1>(module)->InstallAsService(service_manager);
auto profile_manager = std::make_shared<ProfileManager>();
std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager);
std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager);
std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager);
std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager);
}
} // namespace Service::Account

View File

@@ -8,11 +8,15 @@
namespace Service::Account {
class ProfileManager;
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> module, const char* name);
explicit Interface(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager, const char* name);
~Interface() override;
void GetUserCount(Kernel::HLERequestContext& ctx);
void GetUserExistence(Kernel::HLERequestContext& ctx);
@@ -22,9 +26,11 @@ public:
void GetProfile(Kernel::HLERequestContext& ctx);
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> module;
std::shared_ptr<ProfileManager> profile_manager;
};
};

View File

@@ -6,7 +6,8 @@
namespace Service::Account {
ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") {
ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
: Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") {
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"},

View File

@@ -10,7 +10,8 @@ namespace Service::Account {
class ACC_AA final : public Module::Interface {
public:
explicit ACC_AA(std::shared_ptr<Module> module);
explicit ACC_AA(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager);
};
} // namespace Service::Account

View File

@@ -6,7 +6,8 @@
namespace Service::Account {
ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") {
ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
: Module::Interface(std::move(module), std::move(profile_manager), "acc:su") {
static const FunctionInfo functions[] = {
{0, &ACC_SU::GetUserCount, "GetUserCount"},
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
@@ -15,7 +16,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_SU::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"},
{50, nullptr, "IsUserRegistrationRequestPermitted"},
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, nullptr, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
{100, nullptr, "GetUserRegistrationNotifier"},

View File

@@ -11,7 +11,8 @@ namespace Account {
class ACC_SU final : public Module::Interface {
public:
explicit ACC_SU(std::shared_ptr<Module> module);
explicit ACC_SU(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager);
};
} // namespace Account

View File

@@ -6,7 +6,8 @@
namespace Service::Account {
ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") {
ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
: Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") {
static const FunctionInfo functions[] = {
{0, &ACC_U0::GetUserCount, "GetUserCount"},
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
@@ -15,7 +16,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U0::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"},
{50, nullptr, "IsUserRegistrationRequestPermitted"},
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, nullptr, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},

View File

@@ -10,7 +10,8 @@ namespace Service::Account {
class ACC_U0 final : public Module::Interface {
public:
explicit ACC_U0(std::shared_ptr<Module> module);
explicit ACC_U0(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager);
};
} // namespace Service::Account

View File

@@ -6,7 +6,8 @@
namespace Service::Account {
ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") {
ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
: Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") {
static const FunctionInfo functions[] = {
{0, &ACC_U1::GetUserCount, "GetUserCount"},
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
@@ -15,7 +16,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U1::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"},
{50, nullptr, "IsUserRegistrationRequestPermitted"},
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, nullptr, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
{100, nullptr, "GetUserRegistrationNotifier"},

View File

@@ -10,7 +10,8 @@ namespace Service::Account {
class ACC_U1 final : public Module::Interface {
public:
explicit ACC_U1(std::shared_ptr<Module> module);
explicit ACC_U1(std::shared_ptr<Module> module,
std::shared_ptr<ProfileManager> profile_manager);
};
} // namespace Service::Account

View File

@@ -0,0 +1,236 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <random>
#include <boost/optional.hpp>
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
namespace Service::Account {
// TODO(ogniK): Get actual error codes
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
const UUID& UUID::Generate() {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
uuid[0] = distribution(gen);
uuid[1] = distribution(gen);
return *this;
}
ProfileManager::ProfileManager() {
// TODO(ogniK): Create the default user we have for now until loading/saving users is added
auto user_uuid = UUID{1, 0};
CreateNewUser(user_uuid, Settings::values.username);
OpenUser(user_uuid);
}
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
if (user_count >= MAX_USERS) {
return boost::none;
}
profiles[user_count] = user;
return user_count++;
}
/// Deletes a specific profile based on it's profile index
bool ProfileManager::RemoveProfileAtIndex(size_t index) {
if (index >= MAX_USERS || index >= user_count) {
return false;
}
if (index < user_count - 1) {
std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end());
}
profiles.back() = {};
user_count--;
return true;
}
/// Helper function to register a user to the system
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
if (AddToProfiles(user) == boost::none) {
return ERROR_TOO_MANY_USERS;
}
return RESULT_SUCCESS;
}
/// Create a new user on the system. If the uuid of the user already exists, the user is not
/// created.
ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
if (user_count == MAX_USERS) {
return ERROR_TOO_MANY_USERS;
}
if (!uuid) {
return ERROR_ARGUMENT_IS_NULL;
}
if (username[0] == 0x0) {
return ERROR_ARGUMENT_IS_NULL;
}
if (std::any_of(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
return ERROR_USER_ALREADY_EXISTS;
}
ProfileInfo profile;
profile.user_uuid = uuid;
profile.username = username;
profile.data = {};
profile.creation_time = 0x0;
profile.is_open = false;
return AddUser(profile);
}
/// Creates a new user on the system. This function allows a much simpler method of registration
/// specifically by allowing an std::string for the username. This is required specifically since
/// we're loading a string straight from the config
ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
ProfileUsername username_output;
if (username.size() > username_output.size()) {
std::copy_n(username.begin(), username_output.size(), username_output.begin());
} else {
std::copy(username.begin(), username.end(), username_output.begin());
}
return CreateNewUser(uuid, username_output);
}
/// Returns a users profile index based on their user id.
boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
return boost::none;
}
auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
return boost::none;
}
return static_cast<size_t>(std::distance(profiles.begin(), iter));
}
/// Returns a users profile index based on their profile
boost::optional<size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
return GetUserIndex(user.user_uuid);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const {
if (index == boost::none || index >= MAX_USERS) {
return false;
}
const auto& prof_info = profiles[index.get()];
profile.user_uuid = prof_info.user_uuid;
profile.username = prof_info.username;
profile.timestamp = prof_info.creation_time;
return true;
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
auto idx = GetUserIndex(uuid);
return GetProfileBase(idx, profile);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const {
return GetProfileBase(user.user_uuid, profile);
}
/// Returns the current user count on the system. We keep a variable which tracks the count so we
/// don't have to loop the internal profile array every call.
size_t ProfileManager::GetUserCount() const {
return user_count;
}
/// Lists the current "opened" users on the system. Users are typically not open until they sign
/// into something or pick a profile. As of right now users should all be open until qlaunch is
/// booting
size_t ProfileManager::GetOpenUserCount() const {
return std::count_if(profiles.begin(), profiles.end(),
[](const ProfileInfo& p) { return p.is_open; });
}
/// Checks if a user id exists in our profile manager
bool ProfileManager::UserExists(UUID uuid) const {
return (GetUserIndex(uuid) != boost::none);
}
/// Opens a specific user
void ProfileManager::OpenUser(UUID uuid) {
auto idx = GetUserIndex(uuid);
if (idx == boost::none) {
return;
}
profiles[idx.get()].is_open = true;
last_opened_user = uuid;
}
/// Closes a specific user
void ProfileManager::CloseUser(UUID uuid) {
auto idx = GetUserIndex(uuid);
if (idx == boost::none) {
return;
}
profiles[idx.get()].is_open = false;
}
/// Gets all valid user ids on the system
UserIDArray ProfileManager::GetAllUsers() const {
UserIDArray output;
std::transform(profiles.begin(), profiles.end(), output.begin(),
[](const ProfileInfo& p) { return p.user_uuid; });
return output;
}
/// Get all the open users on the system and zero out the rest of the data. This is specifically
/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out
UserIDArray ProfileManager::GetOpenUsers() const {
UserIDArray output;
std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
if (p.is_open)
return p.user_uuid;
return UUID{};
});
std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; });
return output;
}
/// Returns the last user which was opened
UUID ProfileManager::GetLastOpenedUser() const {
return last_opened_user;
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile,
ProfileData& data) const {
if (GetProfileBase(index, profile)) {
data = profiles[index.get()].data;
return true;
}
return false;
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
ProfileData& data) const {
auto idx = GetUserIndex(uuid);
return GetProfileBaseAndData(idx, profile, data);
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
ProfileData& data) const {
return GetProfileBaseAndData(user.user_uuid, profile, data);
}
/// Returns if the system is allowing user registrations or not
bool ProfileManager::CanSystemRegisterUser() const {
return false; // TODO(ogniK): Games shouldn't have
// access to user registration, when we
// emulate qlaunch. Update this to dynamically change.
}
}; // namespace Service::Account

View File

@@ -0,0 +1,117 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "boost/optional.hpp"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
namespace Service::Account {
constexpr size_t MAX_USERS = 8;
constexpr size_t MAX_DATA = 128;
constexpr u128 INVALID_UUID{{0, 0}};
struct UUID {
// UUIDs which are 0 are considered invalid!
u128 uuid = INVALID_UUID;
UUID() = default;
explicit UUID(const u128& id) : uuid{id} {}
explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
explicit operator bool() const {
return uuid != INVALID_UUID;
}
bool operator==(const UUID& rhs) const {
return uuid == rhs.uuid;
}
bool operator!=(const UUID& rhs) const {
return !operator==(rhs);
}
// TODO(ogniK): Properly generate uuids based on RFC-4122
const UUID& Generate();
// Set the UUID to {0,0} to be considered an invalid user
void Invalidate() {
uuid = INVALID_UUID;
}
std::string Format() const {
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
}
};
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
using ProfileUsername = std::array<u8, 0x20>;
using ProfileData = std::array<u8, MAX_DATA>;
using UserIDArray = std::array<UUID, MAX_USERS>;
/// This holds general information about a users profile. This is where we store all the information
/// based on a specific user
struct ProfileInfo {
UUID user_uuid;
ProfileUsername username;
u64 creation_time;
ProfileData data; // TODO(ognik): Work out what this is
bool is_open;
};
struct ProfileBase {
UUID user_uuid;
u64_le timestamp;
ProfileUsername username;
// Zero out all the fields to make the profile slot considered "Empty"
void Invalidate() {
user_uuid.Invalidate();
timestamp = 0;
username.fill(0);
}
};
static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
/// The profile manager is used for handling multiple user profiles at once. It keeps track of open
/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo"
/// objects
class ProfileManager {
public:
ProfileManager(); // TODO(ogniK): Load from system save
ResultCode AddUser(const ProfileInfo& user);
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
ResultCode CreateNewUser(UUID uuid, const std::string& username);
boost::optional<size_t> GetUserIndex(const UUID& uuid) const;
boost::optional<size_t> GetUserIndex(const ProfileInfo& user) const;
bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const;
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile,
ProfileData& data) const;
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
ProfileData& data) const;
size_t GetUserCount() const;
size_t GetOpenUserCount() const;
bool UserExists(UUID uuid) const;
void OpenUser(UUID uuid);
void CloseUser(UUID uuid);
UserIDArray GetOpenUsers() const;
UserIDArray GetAllUsers() const;
UUID GetLastOpenedUser() const;
bool CanSystemRegisterUser() const;
private:
std::array<ProfileInfo, MAX_USERS> profiles{};
size_t user_count = 0;
boost::optional<size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(size_t index);
UUID last_opened_user{INVALID_UUID};
};
}; // namespace Service::Account

View File

@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cinttypes>
#include <stack>
#include "core/core.h"
@@ -17,6 +18,7 @@
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/set/set.h"
#include "core/settings.h"
@@ -145,8 +147,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{51, nullptr, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
{61, nullptr, "SetMediaPlaybackState"},
{62, nullptr, "SetIdleTimeDetectionExtension"},
{63, nullptr, "GetIdleTimeDetectionExtension"},
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
{63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
{64, nullptr, "SetInputDetectionSourceSet"},
{65, nullptr, "ReportUserIsActive"},
{66, nullptr, "GetCurrentIlluminance"},
@@ -281,6 +283,23 @@ void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx)
LOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
idle_time_detection_extension = rp.Pop<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(idle_time_detection_extension);
LOG_WARNING(Service_AM, "(STUBBED) called");
}
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -291,7 +310,7 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
{5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"},
{6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"},
{7, nullptr, "GetCradleStatus"},
{8, nullptr, "GetBootMode"},
{8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
{9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
{10, nullptr, "RequestToAcquireSleepLock"},
{11, nullptr, "ReleaseSleepLock"},
@@ -306,7 +325,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
{52, nullptr, "SwitchLcdBacklight"},
{55, nullptr, "IsInControllerFirmwareUpdateSection"},
{60, nullptr, "GetDefaultDisplayResolution"},
{61, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
{61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent,
"GetDefaultDisplayResolutionChangeEvent"},
{62, nullptr, "GetHdcpAuthenticationState"},
{63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
};
@@ -315,6 +335,15 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
}
void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u8>(static_cast<u8>(Service::PM::SystemBootMode::Normal)); // Normal boot mode
LOG_DEBUG(Service_AM, "called");
}
void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
event->Signal();
@@ -341,6 +370,16 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) {
event->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event);
LOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
const bool use_docked_mode{Settings::values.use_docked_mode};
IPC::ResponseBuilder rb{ctx, 3};
@@ -597,16 +636,16 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
}
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
constexpr u8 data[0x88] = {
constexpr std::array<u8, 0x88> data{{
0xca, 0x97, 0x94, 0xc7, // Magic
1, 0, 0, 0, // IsAccountSelected (bool)
1, 0, 0, 0, // User Id (word 0)
0, 0, 0, 0, // User Id (word 1)
0, 0, 0, 0, // User Id (word 2)
0, 0, 0, 0 // User Id (word 3)
};
}};
std::vector<u8> buffer(data, data + sizeof(data));
std::vector<u8> buffer(data.begin(), data.end());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};

View File

@@ -87,9 +87,12 @@ private:
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::SharedPtr<Kernel::Event> launchable_event;
u32 idle_time_detection_extension = 0;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
@@ -110,8 +113,10 @@ private:
void GetEventHandle(Kernel::HLERequestContext& ctx);
void ReceiveMessage(Kernel::HLERequestContext& ctx);
void GetCurrentFocusState(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx);
void GetOperationMode(Kernel::HLERequestContext& ctx);
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
void GetBootMode(Kernel::HLERequestContext& ctx);
Kernel::SharedPtr<Kernel::Event> event;
};

View File

@@ -7,12 +7,14 @@
#include "common/assert.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_ldr.h"
#include "core/hle/service/filesystem/fsp_pr.h"
@@ -226,6 +228,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
static std::unique_ptr<FileSys::BISFactory> bis_factory;
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
@@ -248,15 +251,35 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
return RESULT_SUCCESS;
}
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
bis_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registred BIS");
return RESULT_SUCCESS;
}
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
LOG_TRACE(Service_FS, "Opening RomFS for current process");
if (romfs_factory == nullptr) {
// TODO(bunnei): Find a better error code for this
return ResultCode(-1);
}
return romfs_factory->Open(title_id);
return romfs_factory->OpenCurrentProcess();
}
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
title_id, static_cast<u8>(storage_id), static_cast<u8>(type));
if (romfs_factory == nullptr) {
// TODO(bunnei): Find a better error code for this
return ResultCode(-1);
}
return romfs_factory->Open(title_id, storage_id, type);
}
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
@@ -281,6 +304,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
return sdmc_factory->Open();
}
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
return bis_factory->GetSystemNANDContents();
}
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
return bis_factory->GetUserNANDContents();
}
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
romfs_factory = nullptr;
save_data_factory = nullptr;
@@ -291,6 +322,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
if (bis_factory == nullptr)
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
save_data_factory = std::move(savedata);

View File

@@ -7,12 +7,23 @@
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/hle/result.h"
namespace FileSys {
class BISFactory;
class RegisteredCache;
class RomFSFactory;
class SaveDataFactory;
class SDMCFactory;
enum class ContentRecordType : u8;
enum class Mode : u32;
enum class SaveDataSpaceId : u8;
enum class StorageId : u8;
struct SaveDataDescriptor;
} // namespace FileSys
namespace Service {
namespace SM {
@@ -24,16 +35,17 @@ namespace FileSystem {
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
// TODO(DarkLordZach): BIS Filesystem
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type);
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSDMC();
// TODO(DarkLordZach): BIS Filesystem
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
/// Registers all Filesystem services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);

View File

@@ -13,9 +13,12 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -23,15 +26,6 @@
namespace Service::FileSystem {
enum class StorageId : u8 {
None = 0,
Host = 1,
GameCard = 2,
NandSystem = 3,
NandUser = 4,
SdCard = 5,
};
class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(FileSys::VirtualFile backend_)
@@ -467,7 +461,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{110, nullptr, "OpenContentStorageFileSystem"},
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
{201, nullptr, "OpenDataStorageByProgramId"},
{202, nullptr, "OpenDataStorageByDataId"},
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
{203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"},
{400, nullptr, "OpenDeviceOperator"},
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
@@ -580,7 +574,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
auto romfs = OpenRomFS(Core::System::GetInstance().CurrentProcess()->program_id);
auto romfs = OpenRomFSCurrentProcess();
if (romfs.Failed()) {
// TODO (bunnei): Find the right error code to use here
LOG_CRITICAL(Service_FS, "no file system interface available!");
@@ -596,10 +590,37 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(std::move(storage));
}
void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto storage_id = rp.PopRaw<FileSys::StorageId>();
const auto unknown = rp.PopRaw<u32>();
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
static_cast<u8>(storage_id), unknown, title_id);
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (data.Failed()) {
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
"could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
static_cast<u8>(storage_id));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1));
return;
}
IStorage storage(std::move(data.Unwrap()));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(storage));
}
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto storage_id = rp.PopRaw<StorageId>();
auto storage_id = rp.PopRaw<FileSys::StorageId>();
auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}",

View File

@@ -25,6 +25,7 @@ private:
void MountSaveData(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenRomStorage(Kernel::HLERequestContext& ctx);
FileSys::VirtualFile romfs;

View File

@@ -92,7 +92,11 @@ private:
// Parse out log metadata
u32 line{};
std::string message, filename, function;
std::string module;
std::string message;
std::string filename;
std::string function;
std::string thread;
while (addr < end_addr) {
const Field field{static_cast<Field>(Memory::Read8(addr++))};
const size_t length{Memory::Read8(addr++)};
@@ -102,6 +106,8 @@ private:
}
switch (field) {
case Field::Skip:
break;
case Field::Message:
message = Memory::ReadCString(addr, length);
break;
@@ -114,6 +120,12 @@ private:
case Field::Function:
function = Memory::ReadCString(addr, length);
break;
case Field::Module:
module = Memory::ReadCString(addr, length);
break;
case Field::Thread:
thread = Memory::ReadCString(addr, length);
break;
}
addr += length;
@@ -128,12 +140,18 @@ private:
if (!filename.empty()) {
log_stream << filename << ':';
}
if (!module.empty()) {
log_stream << module << ':';
}
if (!function.empty()) {
log_stream << function << ':';
}
if (line) {
log_stream << std::to_string(line) << ':';
}
if (!thread.empty()) {
log_stream << thread << ':';
}
if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {
log_stream << ' ';
}
@@ -142,7 +160,7 @@ private:
if (header.IsTailLog()) {
switch (header.severity) {
case MessageHeader::Severity::Trace:
LOG_TRACE(Debug_Emulated, "{}", log_stream.str());
LOG_DEBUG(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Info:
LOG_INFO(Debug_Emulated, "{}", log_stream.str());

View File

@@ -5,31 +5,98 @@
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/romfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/pl_u.h"
namespace Service::NS {
enum class FontArchives : u64 {
Extension = 0x0100000000000810,
Standard = 0x0100000000000811,
Korean = 0x0100000000000812,
ChineseTraditional = 0x0100000000000813,
ChineseSimple = 0x0100000000000814,
};
struct FontRegion {
u32 offset;
u32 size;
};
static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")};
// The below data is specific to shared font data dumped from Switch on f/w 2.2
// Virtual address and offsets/sizes likely will vary by dump
static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
static constexpr u32 EXPECTED_RESULT{
0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
static constexpr u32 EXPECTED_MAGIC{
0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
static constexpr std::array<FontRegion, 6> SHARED_FONT_REGIONS{
FontRegion{0x00000008, 0x001fe764}, FontRegion{0x001fe774, 0x00773e58},
FontRegion{0x009725d4, 0x0001aca8}, FontRegion{0x0098d284, 0x00369cec},
FontRegion{0x00cf6f78, 0x0039b858}, FontRegion{0x010927d8, 0x00019e80},
};
static constexpr FontRegion EMPTY_REGION{0, 0};
std::vector<FontRegion>
SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives
const FontRegion& GetSharedFontRegion(size_t index) {
if (index >= SHARED_FONT_REGIONS.size() || SHARED_FONT_REGIONS.empty()) {
// No font fallback
return EMPTY_REGION;
}
return SHARED_FONT_REGIONS.at(index);
}
enum class LoadState : u32 {
Loading = 0,
Done = 1,
};
void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
"Shared fonts exceeds 17mb!");
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
std::vector<u32> transformed_font(input.size());
// TODO(ogniK): Figure out a better way to do this
std::transform(input.begin(), input.end(), transformed_font.begin(),
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
std::memcpy(output.data() + offset, transformed_font.data(),
transformed_font.size() * sizeof(u32));
offset += transformed_font.size() * sizeof(u32);
}
static u32 GetU32Swapped(const u8* data) {
u32 value;
std::memcpy(&value, data, sizeof(value));
return Common::swap32(value); // Helper function to make BuildSharedFontsRawRegions a bit nicer
}
void BuildSharedFontsRawRegions(const std::vector<u8>& input) {
unsigned cur_offset = 0; // As we can derive the xor key we can just populate the offsets based
// on the shared memory dump
for (size_t i = 0; i < SHARED_FONTS.size(); i++) {
// Out of shared fonts/Invalid font
if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT)
break;
const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^
EXPECTED_MAGIC; // Derive key withing inverse xor
const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
SHARED_FONT_REGIONS.push_back(FontRegion{cur_offset + 8, SIZE});
cur_offset += SIZE + 8;
}
}
PL_U::PL_U() : ServiceFramework("pl:u") {
static const FunctionInfo functions[] = {
{0, &PL_U::RequestLoad, "RequestLoad"},
@@ -40,26 +107,78 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
};
RegisterHandlers(functions);
// Attempt to load shared font data from disk
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SHARED_FONT};
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
if (file.IsOpen()) {
// Read shared font data
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
file.ReadBytes(shared_font->data(), shared_font->size());
const auto nand = FileSystem::GetSystemNANDContents();
// Rebuild shared fonts from data ncas
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
FileSys::ContentRecordType::Data)) {
size_t offset = 0;
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
for (auto font : SHARED_FONTS) {
const auto nca =
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
if (!nca) {
LOG_ERROR(Service_NS, "Failed to find {:016X}! Skipping",
static_cast<u64>(font.first));
continue;
}
const auto romfs = nca->GetRomFS();
if (!romfs) {
LOG_ERROR(Service_NS, "{:016X} has no RomFS! Skipping",
static_cast<u64>(font.first));
continue;
}
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
if (!extracted_romfs) {
LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping",
static_cast<u64>(font.first));
continue;
}
const auto font_fp = extracted_romfs->GetFile(font.second);
if (!font_fp) {
LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping",
static_cast<u64>(font.first), font.second);
continue;
}
std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32));
font_fp->ReadBytes<u32>(font_data_u32.data(), font_fp->GetSize());
// We need to be BigEndian as u32s for the xor encryption
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
Common::swap32);
FontRegion region{
static_cast<u32>(offset + 8),
static_cast<u32>((font_data_u32.size() * sizeof(u32)) -
8)}; // Font offset and size do not account for the header
DecryptSharedFont(font_data_u32, *shared_font, offset);
SHARED_FONT_REGIONS.push_back(region);
}
} else {
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) +
SHARED_FONT};
// Create path if not already created
if (!FileUtil::CreateFullPath(filepath)) {
LOG_ERROR(Service_NS, "Failed to create sharedfonts path \"{}\"!", filepath);
return;
}
FileUtil::IOFile file(filepath, "rb");
shared_font = std::make_shared<std::vector<u8>>(
SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
if (file.IsOpen()) {
// Read shared font data
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
file.ReadBytes(shared_font->data(), shared_font->size());
BuildSharedFontsRawRegions(*shared_font);
} else {
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
}
}
}
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 shared_font_type{rp.Pop<u32>()};
// Games don't call this so all fonts should be loaded
LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -82,7 +201,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size);
rb.Push<u32>(GetSharedFontRegion(font_id).size);
}
void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
@@ -92,14 +211,10 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset);
rb.Push<u32>(GetSharedFontRegion(font_id).offset);
}
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
// TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared
// font data. This (likely) relies on exact address, size, and offsets from the original
// dump. In the future, we need to replace this with a more robust solution.
// Map backing memory for the font data
Core::CurrentProcess()->vm_manager.MapMemoryBlock(
SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared);
@@ -128,8 +243,9 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
// TODO(ogniK): Have actual priority order
for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) {
font_codes.push_back(static_cast<u32>(i));
font_offsets.push_back(SHARED_FONT_REGIONS[i].offset);
font_sizes.push_back(SHARED_FONT_REGIONS[i].size);
auto region = GetSharedFontRegion(i);
font_offsets.push_back(region.offset);
font_sizes.push_back(region.size);
}
ctx.WriteBuffer(font_codes, 0);

View File

@@ -14,7 +14,8 @@ public:
IParentalControlService() : ServiceFramework("IParentalControlService") {
static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"},
{1001, nullptr, "CheckFreeCommunicationPermission"},
{1001, &IParentalControlService::CheckFreeCommunicationPermission,
"CheckFreeCommunicationPermission"},
{1002, nullptr, "ConfirmLaunchApplicationPermission"},
{1003, nullptr, "ConfirmResumeApplicationPermission"},
{1004, nullptr, "ConfirmSnsPostPermission"},
@@ -116,6 +117,12 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(RESULT_SUCCESS);
}
void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
};
void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {

View File

@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/service.h"
namespace Service::PM {
@@ -10,11 +12,20 @@ class BootMode final : public ServiceFramework<BootMode> {
public:
explicit BootMode() : ServiceFramework{"pm:bm"} {
static const FunctionInfo functions[] = {
{0, nullptr, "GetBootMode"},
{0, &BootMode::GetBootMode, "GetBootMode"},
{1, nullptr, "SetMaintenanceBoot"},
};
RegisterHandlers(functions);
}
private:
void GetBootMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(SystemBootMode::Normal)); // Normal boot mode
LOG_DEBUG(Service_PM, "called");
}
};
class DebugMonitor final : public ServiceFramework<DebugMonitor> {

View File

@@ -9,7 +9,7 @@ class ServiceManager;
}
namespace Service::PM {
enum class SystemBootMode : u32 { Normal = 0, Maintenance = 1 };
/// Registers all PM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);

View File

@@ -10,7 +10,7 @@
namespace Service::SM {
void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain");
ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain");
ctx.Session()->ConvertToDomain();
IPC::ResponseBuilder rb{ctx, 3};
@@ -41,7 +41,7 @@ void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0x500);
rb.Push<u16>(0x500);
LOG_WARNING(Service, "(STUBBED) called");
}

View File

@@ -8,6 +8,7 @@
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
#include <ostream>
#include <string>
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -40,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
if (name == "00")
return FileType::NCA;
const std::string extension =
Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
@@ -119,14 +122,9 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{
"There is no control data available.",
};
std::string GetMessageForResultStatus(ResultStatus status) {
return GetMessageForResultStatus(static_cast<u16>(status));
}
std::string GetMessageForResultStatus(u16 status) {
if (status >= 36)
return "";
return RESULT_MESSAGES[status];
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
os << RESULT_MESSAGES.at(static_cast<size_t>(status));
return os;
}
/**

View File

@@ -5,6 +5,7 @@
#pragma once
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
@@ -94,8 +95,7 @@ enum class ResultStatus : u16 {
ErrorNoControl,
};
std::string GetMessageForResultStatus(ResultStatus status);
std::string GetMessageForResultStatus(u16 status);
std::ostream& operator<<(std::ostream& os, ResultStatus status);
/// Interface for loading an application
class AppLoader : NonCopyable {

View File

@@ -3,28 +3,23 @@
// Refer to the license.txt file included.
#include <utility>
#include <vector>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/program_metadata.h"
#include "core/gdbstub/gdbstub.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
#include "core/loader/nso.h"
#include "core/memory.h"
namespace Loader {
AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_)
: AppLoader(std::move(file_)), nca(std::make_unique<FileSys::NCA>(file)) {}
AppLoader_NCA::~AppLoader_NCA() = default;
FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
FileSys::NCA nca(file);
@@ -83,6 +78,4 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
return ResultStatus::Success;
}
AppLoader_NCA::~AppLoader_NCA() = default;
} // namespace Loader

View File

@@ -4,20 +4,24 @@
#pragma once
#include <string>
#include "common/common_types.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/program_metadata.h"
#include "core/file_sys/vfs.h"
#include "core/hle/kernel/object.h"
#include "core/loader/loader.h"
#include "deconstructed_rom_directory.h"
namespace FileSys {
class NCA;
}
namespace Loader {
class AppLoader_DeconstructedRomDirectory;
/// Loads an NCA file
class AppLoader_NCA final : public AppLoader {
public:
explicit AppLoader_NCA(FileSys::VirtualFile file);
~AppLoader_NCA() override;
/**
* Returns the type of the file
@@ -35,12 +39,7 @@ public:
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
~AppLoader_NCA();
private:
FileSys::ProgramMetadata metadata;
FileSys::NCAHeader header;
std::unique_ptr<FileSys::NCA> nca;
std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;
};

View File

@@ -4,22 +4,14 @@
#include <vector>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/core.h"
#include "common/common_types.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/program_metadata.h"
#include "core/file_sys/romfs.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/nso.h"
#include "core/loader/nca.h"
#include "core/loader/xci.h"
#include "core/memory.h"
namespace Loader {

View File

@@ -6,12 +6,18 @@
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
#include "core/loader/nca.h"
namespace FileSys {
class NACP;
class XCI;
} // namespace FileSys
namespace Loader {
class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_XCI final : public AppLoader {
public:
@@ -37,8 +43,6 @@ public:
ResultStatus ReadTitle(std::string& title) override;
private:
FileSys::ProgramMetadata metadata;
std::unique_ptr<FileSys::XCI> xci;
std::unique_ptr<AppLoader_NCA> nca_loader;

View File

@@ -76,22 +76,31 @@ double PerfStats::GetLastFrameTimeScale() {
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
// values increase the time needed to recover and limit framerate again after spikes.
constexpr microseconds MAX_LAG_TIME_US = 25us;
constexpr microseconds MAX_LAG_TIME_US = 25000us;
if (!Settings::values.toggle_framelimit) {
if (!Settings::values.use_frame_limit) {
return;
}
auto now = Clock::now();
frame_limiting_delta_err += current_system_time_us - previous_system_time_us;
const double sleep_scale = Settings::values.frame_limit / 100.0;
// Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
// speed percent or it will clamp too much and prevent this from properly limiting to that
// percent. High values means it'll take longer after a slow frame to recover and start
// limiting
const microseconds max_lag_time_us = duration_cast<microseconds>(
std::chrono::duration<double, std::chrono::microseconds::period>(25ms / sleep_scale));
frame_limiting_delta_err += duration_cast<microseconds>(
std::chrono::duration<double, std::chrono::microseconds::period>(
(current_system_time_us - previous_system_time_us) / sleep_scale));
frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime);
frame_limiting_delta_err =
std::clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US);
std::clamp(frame_limiting_delta_err, -max_lag_time_us, max_lag_time_us);
if (frame_limiting_delta_err > microseconds::zero()) {
std::this_thread::sleep_for(frame_limiting_delta_err);
auto now_after_sleep = Clock::now();
frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now);
now = now_after_sleep;

View File

@@ -130,7 +130,8 @@ struct Values {
// Renderer
float resolution_factor;
bool toggle_framelimit;
bool use_frame_limit;
u16 frame_limit;
bool use_accurate_framebuffers;
float bg_red;

View File

@@ -2,34 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/scm_rev.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#endif
#include "core/core.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
namespace Core {
#ifdef ARCHITECTURE_x86_64
static const char* CpuVendorToStr(Common::CPUVendor vendor) {
switch (vendor) {
case Common::CPUVendor::INTEL:
return "Intel";
case Common::CPUVendor::AMD:
return "Amd";
case Common::CPUVendor::OTHER:
return "Other";
}
UNREACHABLE();
}
#endif
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
return telemetry_id;
@@ -37,8 +19,8 @@ static u64 GenerateTelemetryId() {
u64 GetTelemetryId() {
u64 telemetry_id{};
static const std::string& filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
if (FileUtil::Exists(filename)) {
FileUtil::IOFile file(filename, "rb");
@@ -62,8 +44,8 @@ u64 GetTelemetryId() {
u64 RegenerateTelemetryId() {
const u64 new_telemetry_id{GenerateTelemetryId()};
static const std::string& filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
FileUtil::IOFile file(filename, "wb");
if (!file.IsOpen()) {
@@ -112,48 +94,11 @@ TelemetrySession::TelemetrySession() {
}
// Log application information
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
AddField(Telemetry::FieldType::App, "Git_IsDirty", is_git_dirty);
AddField(Telemetry::FieldType::App, "Git_Branch", Common::g_scm_branch);
AddField(Telemetry::FieldType::App, "Git_Revision", Common::g_scm_rev);
AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date);
AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name);
Telemetry::AppendBuildInfo(field_collection);
// Log user system information
#ifdef ARCHITECTURE_x86_64
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
Common::GetCPUCaps().brand_string);
AddField(Telemetry::FieldType::UserSystem, "CPU_Vendor",
CpuVendorToStr(Common::GetCPUCaps().vendor));
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSSE3",
Common::GetCPUCaps().ssse3);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE41",
Common::GetCPUCaps().sse4_1);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
Common::GetCPUCaps().sse4_2);
#else
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other");
#endif
#ifdef __APPLE__
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple");
#elif defined(_WIN32)
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Windows");
#elif defined(__linux__) || defined(linux) || defined(__linux)
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Linux");
#else
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Unknown");
#endif
// Log user system information
Telemetry::AppendCPUInfo(field_collection);
Telemetry::AppendOSInfo(field_collection);
// Log user configuration information
AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
@@ -161,8 +106,9 @@ TelemetrySession::TelemetrySession() {
Settings::values.use_multi_core);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
Settings::values.resolution_factor);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
Settings::values.toggle_framelimit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
Settings::values.use_frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
Settings::values.use_accurate_framebuffers);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",

View File

@@ -218,13 +218,25 @@ void Maxwell3D::DrawArrays() {
debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
}
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
// Both instance configuration registers can not be set at the same time.
ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont,
"Illegal combination of instancing parameters");
if (regs.draw.instance_next) {
// Increment the current instance *before* drawing.
state.current_instance += 1;
} else if (!regs.draw.instance_cont) {
// Reset the current instance to 0.
state.current_instance = 0;
}
const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
rasterizer.AccelerateDrawBatch(is_indexed);
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
}
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
// the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
// it's possible that it is incorrect and that there is some other register used to specify the

View File

@@ -311,6 +311,36 @@ public:
AlwaysOld = 8,
};
enum class LogicOperation : u32 {
Clear = 0x1500,
And = 0x1501,
AndReverse = 0x1502,
Copy = 0x1503,
AndInverted = 0x1504,
NoOp = 0x1505,
Xor = 0x1506,
Or = 0x1507,
Nor = 0x1508,
Equiv = 0x1509,
Invert = 0x150A,
OrReverse = 0x150B,
CopyInverted = 0x150C,
OrInverted = 0x150D,
Nand = 0x150E,
Set = 0x150F,
};
enum class StencilOp : u32 {
Keep = 1,
Zero = 2,
Replace = 3,
Incr = 4,
Decr = 5,
Invert = 6,
IncrWrap = 7,
DecrWrap = 8,
};
struct Cull {
enum class FrontFace : u32 {
ClockWise = 0x0900,
@@ -489,8 +519,16 @@ public:
float clear_color[4];
float clear_depth;
INSERT_PADDING_WORDS(0x3);
s32 clear_stencil;
INSERT_PADDING_WORDS(0x93);
INSERT_PADDING_WORDS(0x6C);
s32 stencil_back_func_ref;
u32 stencil_back_mask;
u32 stencil_back_func_mask;
INSERT_PADDING_WORDS(0x20);
struct {
u32 address_high;
@@ -554,16 +592,14 @@ public:
u32 enable[NumRenderTargets];
} blend;
struct {
u32 enable;
u32 front_op_fail;
u32 front_op_zfail;
u32 front_op_zpass;
u32 front_func_func;
u32 front_func_ref;
u32 front_func_mask;
u32 front_mask;
} stencil;
u32 stencil_enable;
StencilOp stencil_front_op_fail;
StencilOp stencil_front_op_zfail;
StencilOp stencil_front_op_zpass;
ComparisonOp stencil_front_func_func;
s32 stencil_front_func_ref;
u32 stencil_front_func_mask;
u32 stencil_front_mask;
INSERT_PADDING_WORDS(0x3);
@@ -607,13 +643,11 @@ public:
INSERT_PADDING_WORDS(0x5);
struct {
u32 enable;
u32 back_op_fail;
u32 back_op_zfail;
u32 back_op_zpass;
u32 back_func_func;
} stencil_two_side;
u32 stencil_two_side_enable;
StencilOp stencil_back_op_fail;
StencilOp stencil_back_op_zfail;
StencilOp stencil_back_op_zpass;
ComparisonOp stencil_back_func_func;
INSERT_PADDING_WORDS(0x17);
@@ -638,6 +672,8 @@ public:
union {
u32 vertex_begin_gl;
BitField<0, 16, PrimitiveTopology> topology;
BitField<26, 1, u32> instance_next;
BitField<27, 1, u32> instance_cont;
};
} draw;
@@ -677,11 +713,30 @@ public:
INSERT_PADDING_WORDS(0x7);
INSERT_PADDING_WORDS(0x46);
INSERT_PADDING_WORDS(0x20);
struct {
u32 is_instanced[NumVertexArrays];
/// Returns whether the vertex array specified by index is supposed to be
/// accessed per instance or not.
bool IsInstancingEnabled(u32 index) const {
return is_instanced[index];
}
} instanced_arrays;
INSERT_PADDING_WORDS(0x6);
Cull cull;
INSERT_PADDING_WORDS(0x2B);
INSERT_PADDING_WORDS(0x28);
struct {
u32 enable;
LogicOperation operation;
} logic_op;
INSERT_PADDING_WORDS(0x1);
union {
u32 raw;
@@ -830,6 +885,7 @@ public:
};
std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages;
u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering.
};
State state{};
@@ -903,6 +959,10 @@ ASSERT_REG_POSITION(viewport, 0x300);
ASSERT_REG_POSITION(vertex_buffer, 0x35D);
ASSERT_REG_POSITION(clear_color[0], 0x360);
ASSERT_REG_POSITION(clear_depth, 0x364);
ASSERT_REG_POSITION(clear_stencil, 0x368);
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
ASSERT_REG_POSITION(zeta, 0x3F8);
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
ASSERT_REG_POSITION(rt_control, 0x487);
@@ -914,18 +974,31 @@ ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
ASSERT_REG_POSITION(depth_test_func, 0x4C3);
ASSERT_REG_POSITION(blend, 0x4CF);
ASSERT_REG_POSITION(stencil, 0x4E0);
ASSERT_REG_POSITION(stencil_enable, 0x4E0);
ASSERT_REG_POSITION(stencil_front_op_fail, 0x4E1);
ASSERT_REG_POSITION(stencil_front_op_zfail, 0x4E2);
ASSERT_REG_POSITION(stencil_front_op_zpass, 0x4E3);
ASSERT_REG_POSITION(stencil_front_func_func, 0x4E4);
ASSERT_REG_POSITION(stencil_front_func_ref, 0x4E5);
ASSERT_REG_POSITION(stencil_front_func_mask, 0x4E6);
ASSERT_REG_POSITION(stencil_front_mask, 0x4E7);
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
ASSERT_REG_POSITION(vb_element_base, 0x50D);
ASSERT_REG_POSITION(zeta_enable, 0x54E);
ASSERT_REG_POSITION(tsc, 0x557);
ASSERT_REG_POSITION(tic, 0x55D);
ASSERT_REG_POSITION(stencil_two_side, 0x565);
ASSERT_REG_POSITION(stencil_two_side_enable, 0x565);
ASSERT_REG_POSITION(stencil_back_op_fail, 0x566);
ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567);
ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568);
ASSERT_REG_POSITION(stencil_back_func_func, 0x569);
ASSERT_REG_POSITION(point_coord_replace, 0x581);
ASSERT_REG_POSITION(code_address, 0x582);
ASSERT_REG_POSITION(draw, 0x585);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(cull, 0x646);
ASSERT_REG_POSITION(logic_op, 0x671);
ASSERT_REG_POSITION(clear_buffers, 0x674);
ASSERT_REG_POSITION(query, 0x6C0);
ASSERT_REG_POSITION(vertex_array[0], 0x700);

View File

@@ -12,6 +12,7 @@
#include <boost/optional.hpp>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -79,6 +80,9 @@ union Attribute {
// shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval
// shader.
TessCoordInstanceIDVertexID = 47,
// This attribute contains a tuple of (Unk, Unk, Unk, gl_FrontFacing) when inside a fragment
// shader. It is unknown what the other values contain.
FrontFacing = 63,
};
union {
@@ -141,6 +145,7 @@ enum class PredCondition : u64 {
NotEqual = 5,
GreaterEqual = 6,
LessThanWithNan = 9,
GreaterThanWithNan = 12,
NotEqualWithNan = 13,
// TODO(Subv): Other condition types
};
@@ -213,6 +218,18 @@ enum class FlowCondition : u64 {
Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for?
};
enum class PredicateResultMode : u64 {
None = 0x0,
NotZero = 0x3,
};
enum class TextureType : u64 {
Texture1D = 0,
Texture2D = 1,
Texture3D = 2,
TextureCube = 3,
};
union Instruction {
Instruction& operator=(const Instruction& instr) {
value = instr.value;
@@ -253,7 +270,7 @@ union Instruction {
BitField<39, 1, u64> invert_a;
BitField<40, 1, u64> invert_b;
BitField<41, 2, LogicOperation> operation;
BitField<44, 2, u64> unk44;
BitField<44, 2, PredicateResultMode> pred_result_mode;
BitField<48, 3, Pred> pred48;
} lop;
@@ -263,6 +280,19 @@ union Instruction {
BitField<56, 1, u64> invert_b;
} lop32i;
union {
BitField<28, 8, u64> imm_lut28;
BitField<48, 8, u64> imm_lut48;
u32 GetImmLut28() const {
return static_cast<u32>(imm_lut28);
}
u32 GetImmLut48() const {
return static_cast<u32>(imm_lut48);
}
} lop3;
u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
@@ -282,6 +312,10 @@ union Instruction {
}
} alu;
union {
BitField<48, 1, u64> negate_b;
} fmul;
union {
BitField<48, 1, u64> is_signed;
} shift;
@@ -420,6 +454,8 @@ union Instruction {
} conversion;
union {
BitField<28, 1, u64> array;
BitField<29, 2, TextureType> texture_type;
BitField<31, 4, u64> component_mask;
bool IsComponentEnabled(size_t component) const {
@@ -428,28 +464,91 @@ union Instruction {
} tex;
union {
BitField<50, 3, u64> component_mask_selector;
BitField<28, 1, u64> array;
BitField<29, 2, TextureType> texture_type;
BitField<56, 2, u64> component;
} tld4;
union {
BitField<52, 2, u64> component;
} tld4s;
union {
BitField<0, 8, Register> gpr0;
BitField<28, 8, Register> gpr28;
BitField<50, 3, u64> component_mask_selector;
BitField<53, 4, u64> texture_info;
TextureType GetTextureType() const {
// The TEXS instruction has a weird encoding for the texture type.
if (texture_info == 0)
return TextureType::Texture1D;
if (texture_info >= 1 && texture_info <= 9)
return TextureType::Texture2D;
if (texture_info >= 10 && texture_info <= 11)
return TextureType::Texture3D;
if (texture_info >= 12 && texture_info <= 13)
return TextureType::TextureCube;
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
}
bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info >= 7 && texture_info <= 9;
}
bool HasTwoDestinations() const {
return gpr28.Value() != Register::ZeroIndex;
}
bool IsComponentEnabled(size_t component) const {
static constexpr std::array<std::array<u32, 8>, 4> mask_lut{
{{},
{0x1, 0x2, 0x4, 0x8, 0x3},
{0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc},
{0x7, 0xb, 0xd, 0xe, 0xf}}};
static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{
{},
{0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc},
{0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc},
{0x7, 0xb, 0xd, 0xe, 0xf},
}};
size_t index{gpr0.Value() != Register::ZeroIndex ? 1U : 0U};
index |= gpr28.Value() != Register::ZeroIndex ? 2 : 0;
return ((1ull << component) & mask_lut[index][component_mask_selector]) != 0;
u32 mask = mask_lut[index][component_mask_selector];
// A mask of 0 means this instruction uses an unimplemented mask.
ASSERT(mask != 0);
return ((1ull << component) & mask) != 0;
}
} texs;
union {
BitField<53, 4, u64> texture_info;
TextureType GetTextureType() const {
// The TLDS instruction has a weird encoding for the texture type.
if (texture_info >= 0 && texture_info <= 1) {
return TextureType::Texture1D;
}
if (texture_info == 2 || texture_info == 8 || texture_info == 12 ||
(texture_info >= 4 && texture_info <= 6)) {
return TextureType::Texture2D;
}
if (texture_info == 7) {
return TextureType::Texture3D;
}
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
}
bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info == 8;
}
} tlds;
union {
BitField<20, 24, u64> target;
BitField<5, 1, u64> constant_buffer;
@@ -512,10 +611,14 @@ public:
LD_A,
LD_C,
ST_A,
LDG, // Load from global memory
STG, // Store in global memory
TEX,
TEXQ, // Texture Query
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
TLDS, // Texture Load with scalar/non-vec4 source/destinations
TEXQ, // Texture Query
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
TLDS, // Texture Load with scalar/non-vec4 source/destinations
TLD4, // Texture Load 4
TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations
EXIT,
IPA,
FFMA_IMM, // Fused Multiply and Add
@@ -560,6 +663,9 @@ public:
LOP_R,
LOP_IMM,
LOP32I,
LOP3_C,
LOP3_R,
LOP3_IMM,
MOV_C,
MOV_R,
MOV_IMM,
@@ -723,10 +829,14 @@ private:
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"),
INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"),
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
@@ -778,6 +888,9 @@ private:
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
INST("0011110---------", Id::LOP3_IMM, Type::ArithmeticInteger, "LOP3_IMM"),
INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"),
INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"),
INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"),

View File

@@ -55,6 +55,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
case RenderTargetFormat::RGBA8_UNORM:
case RenderTargetFormat::RGBA8_SNORM:
case RenderTargetFormat::RGBA8_SRGB:
case RenderTargetFormat::RGBA8_UINT:
case RenderTargetFormat::RGB10_A2_UNORM:
case RenderTargetFormat::BGRA8_UNORM:
case RenderTargetFormat::RG16_UNORM:

View File

@@ -30,6 +30,7 @@ enum class RenderTargetFormat : u32 {
RGBA8_UNORM = 0xD5,
RGBA8_SRGB = 0xD6,
RGBA8_SNORM = 0xD7,
RGBA8_UINT = 0xD9,
RG16_UNORM = 0xDA,
RG16_SNORM = 0xDB,
RG16_SINT = 0xDC,

View File

@@ -8,8 +8,6 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
struct ScreenInfo;
namespace VideoCore {
class RasterizerInterface {
@@ -55,7 +53,7 @@ public:
/// Attempt to use a faster method to display the framebuffer to screen
virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride, ScreenInfo& screen_info) {
u32 pixel_stride) {
return false;
}

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "core/frontend/emu_window.h"
#include "core/settings.h"
#include "video_core/renderer_base.h"
@@ -17,16 +16,9 @@ RendererBase::RendererBase(Core::Frontend::EmuWindow& window) : render_window{wi
RendererBase::~RendererBase() = default;
void RendererBase::RefreshBaseSettings() {
RefreshRasterizerSetting();
UpdateCurrentFramebufferLayout();
renderer_settings.use_framelimiter = Settings::values.toggle_framelimit;
}
void RendererBase::RefreshRasterizerSetting() {
if (rasterizer == nullptr) {
rasterizer = std::make_unique<RasterizerOpenGL>(render_window);
}
renderer_settings.use_framelimiter = Settings::values.use_frame_limit;
}
void RendererBase::UpdateCurrentFramebufferLayout() {

View File

@@ -58,9 +58,6 @@ public:
void RefreshBaseSettings();
protected:
/// Refreshes settings specific to the rasterizer.
void RefreshRasterizerSetting();
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer

View File

@@ -14,6 +14,7 @@
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
#include "core/hle/kernel/process.h"
@@ -25,6 +26,8 @@
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
namespace OpenGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using PixelFormat = SurfaceParams::PixelFormat;
using SurfaceType = SurfaceParams::SurfaceType;
@@ -36,8 +39,8 @@ MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window)
: emu_window{window}, stream_buffer(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE) {
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: emu_window{window}, screen_info{info}, stream_buffer(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE) {
// Create sampler objects
for (size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -98,7 +101,8 @@ RasterizerOpenGL::~RasterizerOpenGL() {}
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
GLintptr buffer_offset) {
MICROPROFILE_SCOPE(OpenGL_VAO);
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
const auto& regs = gpu.regs;
state.draw.vertex_array = hw_vao.handle;
state.draw.vertex_buffer = stream_buffer.GetHandle();
@@ -110,9 +114,13 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
if (!vertex_array.IsEnabled())
continue;
const Tegra::GPUVAddr start = vertex_array.StartAddress();
Tegra::GPUVAddr start = vertex_array.StartAddress();
const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor);
}
ASSERT(end > start);
u64 size = end - start + 1;
@@ -124,7 +132,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset,
vertex_array.stride);
ASSERT_MSG(vertex_array.divisor == 0, "Vertex buffer divisor unimplemented");
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
// Tell OpenGL that this is an instanced vertex buffer to prevent accessing different
// indexes on each vertex. We do the instance indexing manually by incrementing the
// start address of the vertex buffer.
glVertexBindingDivisor(index, 1);
} else {
// Disable the vertex buffer instancing.
glVertexBindingDivisor(index, 0);
}
}
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
@@ -166,7 +182,7 @@ static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Fetch program code from memory
GLShader::ProgramCode program_code;
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH);
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
@@ -291,7 +307,8 @@ bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
}
std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb,
bool using_depth_fb) {
bool using_depth_fb,
bool preserve_contents) {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) {
@@ -299,22 +316,20 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
using_color_fb = false;
}
// TODO(bunnei): Implement this
const bool has_stencil = false;
const bool has_stencil = regs.stencil_enable;
const bool write_color_fb =
state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
const bool write_depth_fb =
(state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
(has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0);
(has_stencil && (state.stencil.front.write_mask || state.stencil.back.write_mask));
Surface color_surface;
Surface depth_surface;
MathUtil::Rectangle<u32> surfaces_rect;
std::tie(color_surface, depth_surface, surfaces_rect) =
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, preserve_contents);
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
const MathUtil::Rectangle<u32> draw_rect{
@@ -348,41 +363,70 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
}
void RasterizerOpenGL::Clear() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
const auto prev_state{state};
SCOPE_EXIT({ prev_state.Apply(); });
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
bool use_color_fb = false;
bool use_depth_fb = false;
GLbitfield clear_mask = 0;
if (regs.clear_buffers.R && regs.clear_buffers.G && regs.clear_buffers.B &&
OpenGLState clear_state;
clear_state.draw.draw_framebuffer = state.draw.draw_framebuffer;
clear_state.color_mask.red_enabled = regs.clear_buffers.R ? GL_TRUE : GL_FALSE;
clear_state.color_mask.green_enabled = regs.clear_buffers.G ? GL_TRUE : GL_FALSE;
clear_state.color_mask.blue_enabled = regs.clear_buffers.B ? GL_TRUE : GL_FALSE;
clear_state.color_mask.alpha_enabled = regs.clear_buffers.A ? GL_TRUE : GL_FALSE;
GLbitfield clear_mask{};
if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A) {
clear_mask |= GL_COLOR_BUFFER_BIT;
use_color_fb = true;
if (regs.clear_buffers.RT == 0) {
// We only support clearing the first color attachment for now
clear_mask |= GL_COLOR_BUFFER_BIT;
use_color_fb = true;
} else {
// TODO(subv): Add support for the other color attachments
LOG_CRITICAL(HW_GPU, "Clear unimplemented for RT {}", regs.clear_buffers.RT);
}
}
if (regs.clear_buffers.Z) {
ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear Z but buffer is not enabled!");
use_depth_fb = true;
clear_mask |= GL_DEPTH_BUFFER_BIT;
use_depth_fb = regs.zeta_enable != 0;
// Always enable the depth write when clearing the depth buffer. The depth write mask is
// ignored when clearing the buffer in the Switch, but OpenGL obeys it so we set it to true.
state.depth.test_enabled = true;
state.depth.write_mask = GL_TRUE;
state.depth.test_func = GL_ALWAYS;
state.Apply();
clear_state.depth.test_enabled = true;
clear_state.depth.test_func = GL_ALWAYS;
}
if (regs.clear_buffers.S) {
ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!");
use_depth_fb = true;
clear_mask |= GL_STENCIL_BUFFER_BIT;
clear_state.stencil.test_enabled = true;
}
if (clear_mask == 0)
if (!use_color_fb && !use_depth_fb) {
// No color surface nor depth/stencil surface are enabled
return;
}
if (clear_mask == 0) {
// No clear mask is enabled
return;
}
ScopeAcquireGLContext acquire_context{emu_window};
auto [dirty_color_surface, dirty_depth_surface] =
ConfigureFramebuffers(use_color_fb, use_depth_fb);
ConfigureFramebuffers(use_color_fb, use_depth_fb, false);
clear_state.Apply();
// TODO(Subv): Support clearing only partial colors.
glClearColor(regs.clear_color[0], regs.clear_color[1], regs.clear_color[2],
regs.clear_color[3]);
glClearDepth(regs.clear_depth);
glClearStencil(regs.clear_stencil);
glClear(clear_mask);
@@ -432,10 +476,12 @@ void RasterizerOpenGL::DrawArrays() {
ScopeAcquireGLContext acquire_context{emu_window};
auto [dirty_color_surface, dirty_depth_surface] =
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0);
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true);
SyncDepthTestState();
SyncStencilTestState();
SyncBlendState();
SyncLogicOpState();
SyncCullMode();
// TODO(bunnei): Sync framebuffer_scale uniform here
@@ -561,8 +607,7 @@ bool RasterizerOpenGL::AccelerateFill(const void* config) {
}
bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
VAddr framebuffer_addr, u32 pixel_stride,
ScreenInfo& screen_info) {
VAddr framebuffer_addr, u32 pixel_stride) {
if (!framebuffer_addr) {
return {};
}
@@ -825,6 +870,34 @@ void RasterizerOpenGL::SyncDepthTestState() {
state.depth.test_func = MaxwellToGL::ComparisonOp(regs.depth_test_func);
}
void RasterizerOpenGL::SyncStencilTestState() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
state.stencil.test_enabled = regs.stencil_enable != 0;
if (!regs.stencil_enable) {
return;
}
// TODO(bunnei): Verify behavior when this is not set
ASSERT(regs.stencil_two_side_enable);
state.stencil.front.test_func = MaxwellToGL::ComparisonOp(regs.stencil_front_func_func);
state.stencil.front.test_ref = regs.stencil_front_func_ref;
state.stencil.front.test_mask = regs.stencil_front_func_mask;
state.stencil.front.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_fail);
state.stencil.front.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_zfail);
state.stencil.front.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_front_op_zpass);
state.stencil.front.write_mask = regs.stencil_front_mask;
state.stencil.back.test_func = MaxwellToGL::ComparisonOp(regs.stencil_back_func_func);
state.stencil.back.test_ref = regs.stencil_back_func_ref;
state.stencil.back.test_mask = regs.stencil_back_func_mask;
state.stencil.back.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_fail);
state.stencil.back.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_zfail);
state.stencil.back.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_back_op_zpass);
state.stencil.back.write_mask = regs.stencil_back_mask;
}
void RasterizerOpenGL::SyncBlendState() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
@@ -834,6 +907,9 @@ void RasterizerOpenGL::SyncBlendState() {
if (!state.blend.enabled)
return;
ASSERT_MSG(regs.logic_op.enable == 0,
"Blending and logic op can't be enabled at the same time.");
ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented");
ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented");
state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb);
@@ -843,3 +919,19 @@ void RasterizerOpenGL::SyncBlendState() {
state.blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_a);
state.blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_a);
}
void RasterizerOpenGL::SyncLogicOpState() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
// TODO(Subv): Support more than just render target 0.
state.logic_op.enabled = regs.logic_op.enable != 0;
if (!state.logic_op.enabled)
return;
ASSERT_MSG(regs.blend.enable == 0, "Blending and logic op can't be enabled at the same time.");
state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
}
} // namespace OpenGL

View File

@@ -22,15 +22,17 @@
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
struct ScreenInfo;
namespace Core::Frontend {
class EmuWindow;
}
namespace OpenGL {
struct ScreenInfo;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
explicit RasterizerOpenGL(Core::Frontend::EmuWindow& renderer);
explicit RasterizerOpenGL(Core::Frontend::EmuWindow& renderer, ScreenInfo& info);
~RasterizerOpenGL() override;
void DrawArrays() override;
@@ -43,8 +45,8 @@ public:
bool AccelerateDisplayTransfer(const void* config) override;
bool AccelerateTextureCopy(const void* config) override;
bool AccelerateFill(const void* config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr,
u32 pixel_stride, ScreenInfo& screen_info) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
bool AccelerateDrawBatch(bool is_indexed) override;
/// OpenGL shader generated for a given Maxwell register state
@@ -87,7 +89,8 @@ private:
/// Configures the color and depth framebuffer states and returns the dirty <Color, Depth>
/// surfaces if writing was enabled.
std::pair<Surface, Surface> ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb);
std::pair<Surface, Surface> ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb,
bool preserve_contents);
/// Binds the framebuffer color and depth surface
void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
@@ -138,9 +141,15 @@ private:
/// Syncs the depth test state to match the guest state
void SyncDepthTestState();
/// Syncs the stencil test state to match the guest state
void SyncStencilTestState();
/// Syncs the blend state to match the guest state
void SyncBlendState();
/// Syncs the LogicOp state to match the guest state
void SyncLogicOpState();
bool has_ARB_direct_state_access = false;
bool has_ARB_separate_shader_objects = false;
bool has_ARB_vertex_attrib_binding = false;
@@ -151,6 +160,8 @@ private:
Core::Frontend::EmuWindow& emu_window;
ScreenInfo& screen_info;
std::unique_ptr<GLShader::ProgramManager> shader_program_manager;
OGLVertexArray sw_vao;
OGLVertexArray hw_vao;
@@ -178,3 +189,5 @@ private:
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;
};
} // namespace OpenGL

View File

@@ -19,6 +19,8 @@
#include "video_core/textures/decoders.h"
#include "video_core/utils.h"
namespace OpenGL {
using SurfaceType = SurfaceParams::SurfaceType;
using PixelFormat = SurfaceParams::PixelFormat;
using ComponentType = SurfaceParams::ComponentType;
@@ -94,6 +96,7 @@ struct FormatTuple {
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
{GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
{GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm,
false}, // A2B10G10R10U
@@ -142,14 +145,16 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
// Depth formats
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm,
false}, // Z16
// DepthStencil formats
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm,
false}, // Z24S8
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm,
false}, // S8Z24
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm,
false}, // Z16
false}, // S8Z24
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV,
ComponentType::Float, false}, // Z32FS8
}};
@@ -243,6 +248,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
// clang-format off
MortonCopy<true, PixelFormat::ABGR8U>,
MortonCopy<true, PixelFormat::ABGR8S>,
MortonCopy<true, PixelFormat::ABGR8UI>,
MortonCopy<true, PixelFormat::B5G6R5U>,
MortonCopy<true, PixelFormat::A2B10G10R10U>,
MortonCopy<true, PixelFormat::A1B5G5R5U>,
@@ -283,10 +289,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::RG32UI>,
MortonCopy<true, PixelFormat::R32UI>,
MortonCopy<true, PixelFormat::Z24S8>,
MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z24S8>,
MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32FS8>,
// clang-format on
};
@@ -297,6 +303,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
// clang-format off
MortonCopy<false, PixelFormat::ABGR8U>,
MortonCopy<false, PixelFormat::ABGR8S>,
MortonCopy<false, PixelFormat::ABGR8UI>,
MortonCopy<false, PixelFormat::B5G6R5U>,
MortonCopy<false, PixelFormat::A2B10G10R10U>,
MortonCopy<false, PixelFormat::A1B5G5R5U>,
@@ -339,10 +346,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::RG32UI>,
MortonCopy<false, PixelFormat::R32UI>,
MortonCopy<false, PixelFormat::Z24S8>,
MortonCopy<false, PixelFormat::S8Z24>,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z24S8>,
MortonCopy<false, PixelFormat::S8Z24>,
MortonCopy<false, PixelFormat::Z32FS8>,
// clang-format on
};
@@ -681,7 +688,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu
}
SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool using_color_fb,
bool using_depth_fb) {
bool using_depth_fb,
bool preserve_contents) {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
// TODO(bunnei): This is hard corded to use just the first render buffer
@@ -703,7 +711,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool usin
MathUtil::Rectangle<u32> color_rect{};
Surface color_surface;
if (using_color_fb) {
color_surface = GetSurface(color_params);
color_surface = GetSurface(color_params, preserve_contents);
if (color_surface) {
color_rect = color_surface->GetSurfaceParams().GetRect();
}
@@ -712,7 +720,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool usin
MathUtil::Rectangle<u32> depth_rect{};
Surface depth_surface;
if (using_depth_fb) {
depth_surface = GetSurface(depth_params);
depth_surface = GetSurface(depth_params, preserve_contents);
if (depth_surface) {
depth_rect = depth_surface->GetSurfaceParams().GetRect();
}
@@ -747,7 +755,7 @@ void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
surface->FlushGLBuffer();
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params) {
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
if (params.addr == 0 || params.height * params.width == 0) {
return {};
}
@@ -769,16 +777,33 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params) {
} else if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
// Use the cached surface as-is
return surface;
} else if (preserve_contents) {
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
UnregisterSurface(surface);
Surface new_surface{RecreateSurface(surface, params)};
RegisterSurface(new_surface);
return new_surface;
} else {
// If surface parameters changed, recreate the surface from the old one
return RecreateSurface(surface, params);
// Delete the old surface before creating a new one to prevent collisions.
UnregisterSurface(surface);
}
}
// Try to get a previously reserved surface
surface = TryGetReservedSurface(params);
// No surface found - create a new one
surface = std::make_shared<CachedSurface>(params);
RegisterSurface(surface);
LoadSurface(surface);
if (!surface) {
surface = std::make_shared<CachedSurface>(params);
ReserveSurface(surface);
RegisterSurface(surface);
}
// Only load surface from memory if we care about the contents
if (preserve_contents) {
LoadSurface(surface);
}
return surface;
}
@@ -787,19 +812,76 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
const auto& params{surface->GetSurfaceParams()};
ASSERT(params.type == new_params.type);
ASSERT(params.pixel_format == new_params.pixel_format);
ASSERT(params.component_type == new_params.component_type);
// Create a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{std::make_shared<CachedSurface>(new_params)};
BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle,
new_surface->GetSurfaceParams().GetRect(), params.type, read_framebuffer.handle,
draw_framebuffer.handle);
// Update cache accordingly
UnregisterSurface(surface);
RegisterSurface(new_surface);
// If format is unchanged, we can do a faster blit without reinterpreting pixel data
if (params.pixel_format == new_params.pixel_format) {
BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle,
new_surface->GetSurfaceParams().GetRect(), params.type,
read_framebuffer.handle, draw_framebuffer.handle);
return new_surface;
}
auto source_format = GetFormatTuple(params.pixel_format, params.component_type);
auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type);
size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes());
// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one
// using the new format.
OGLBuffer pbo;
pbo.Create();
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
if (source_format.compressed) {
glGetCompressedTextureImage(surface->Texture().handle, 0,
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
} else {
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
if (params.SizeInBytes() < new_params.SizeInBytes()) {
// Upload the rest of the memory.
if (new_params.is_tiled) {
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest of
// the data in this case. Games like Super Mario Odyssey seem to hit this case when
// drawing, it re-uses the memory of a previous texture as a bigger framebuffer but it
// doesn't clear it beforehand, the texture is already full of zeros.
LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during "
"reinterpretation but the texture is tiled.");
}
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
auto address = Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(
new_params.addr + params.SizeInBytes());
std::vector<u8> data(remaining_size);
Memory::ReadBlock(*address, data.data(), data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, params.SizeInBytes(), remaining_size, data.data());
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
const auto& dest_rect{new_params.GetRect()};
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
if (dest_format.compressed) {
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
static_cast<GLsizei>(dest_rect.GetWidth()),
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
} else {
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
static_cast<GLsizei>(dest_rect.GetWidth()),
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
dest_format.type, nullptr);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
pbo.Release();
return new_surface;
}
@@ -875,6 +957,21 @@ void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
surface_cache.erase(search);
}
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
const auto& surface_reserve_key{SurfaceReserveKey::Create(surface->GetSurfaceParams())};
surface_reserve[surface_reserve_key] = surface;
}
Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params) {
const auto& surface_reserve_key{SurfaceReserveKey::Create(params)};
auto search{surface_reserve.find(surface_reserve_key)};
if (search != surface_reserve.end()) {
RegisterSurface(search->second);
return search->second;
}
return {};
}
template <typename Map, typename Interval>
constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
@@ -913,3 +1010,5 @@ void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 siz
if (delta < 0)
cached_pages.add({pages_interval, delta});
}
} // namespace OpenGL

View File

@@ -11,11 +11,14 @@
#include <boost/icl/interval_map.hpp>
#include "common/common_types.h"
#include "common/hash.h"
#include "common/math_util.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
class CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
@@ -25,55 +28,60 @@ struct SurfaceParams {
enum class PixelFormat {
ABGR8U = 0,
ABGR8S = 1,
B5G6R5U = 2,
A2B10G10R10U = 3,
A1B5G5R5U = 4,
R8U = 5,
R8UI = 6,
RGBA16F = 7,
RGBA16U = 8,
RGBA16UI = 9,
R11FG11FB10F = 10,
RGBA32UI = 11,
DXT1 = 12,
DXT23 = 13,
DXT45 = 14,
DXN1 = 15, // This is also known as BC4
DXN2UNORM = 16,
DXN2SNORM = 17,
BC7U = 18,
ASTC_2D_4X4 = 19,
G8R8U = 20,
G8R8S = 21,
BGRA8 = 22,
RGBA32F = 23,
RG32F = 24,
R32F = 25,
R16F = 26,
R16U = 27,
R16S = 28,
R16UI = 29,
R16I = 30,
RG16 = 31,
RG16F = 32,
RG16UI = 33,
RG16I = 34,
RG16S = 35,
RGB32F = 36,
SRGBA8 = 37,
RG8U = 38,
RG8S = 39,
RG32UI = 40,
R32UI = 41,
ABGR8UI = 2,
B5G6R5U = 3,
A2B10G10R10U = 4,
A1B5G5R5U = 5,
R8U = 6,
R8UI = 7,
RGBA16F = 8,
RGBA16U = 9,
RGBA16UI = 10,
R11FG11FB10F = 11,
RGBA32UI = 12,
DXT1 = 13,
DXT23 = 14,
DXT45 = 15,
DXN1 = 16, // This is also known as BC4
DXN2UNORM = 17,
DXN2SNORM = 18,
BC7U = 19,
ASTC_2D_4X4 = 20,
G8R8U = 21,
G8R8S = 22,
BGRA8 = 23,
RGBA32F = 24,
RG32F = 25,
R32F = 26,
R16F = 27,
R16U = 28,
R16S = 29,
R16UI = 30,
R16I = 31,
RG16 = 32,
RG16F = 33,
RG16UI = 34,
RG16I = 35,
RG16S = 36,
RGB32F = 37,
SRGBA8 = 38,
RG8U = 39,
RG8S = 40,
RG32UI = 41,
R32UI = 42,
MaxColorFormat,
// Depth formats
Z32F = 43,
Z16 = 44,
MaxDepthFormat,
// DepthStencil formats
Z24S8 = 42,
S8Z24 = 43,
Z32F = 44,
Z16 = 45,
Z32FS8 = 46,
Z24S8 = 45,
S8Z24 = 46,
Z32FS8 = 47,
MaxDepthStencilFormat,
@@ -113,6 +121,7 @@ struct SurfaceParams {
constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
@@ -153,10 +162,10 @@ struct SurfaceParams {
1, // RG8S
1, // RG32UI
1, // R32UI
1, // Z24S8
1, // S8Z24
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
@@ -171,6 +180,7 @@ struct SurfaceParams {
constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
32, // ABGR8U
32, // ABGR8S
32, // ABGR8UI
16, // B5G6R5U
32, // A2B10G10R10U
16, // A1B5G5R5U
@@ -211,10 +221,10 @@ struct SurfaceParams {
16, // RG8S
64, // RG32UI
32, // R32UI
32, // Z24S8
32, // S8Z24
32, // Z32F
16, // Z16
32, // Z24S8
32, // S8Z24
64, // Z32FS8
}};
@@ -253,6 +263,8 @@ struct SurfaceParams {
return PixelFormat::ABGR8U;
case Tegra::RenderTargetFormat::RGBA8_SNORM:
return PixelFormat::ABGR8S;
case Tegra::RenderTargetFormat::RGBA8_UINT:
return PixelFormat::ABGR8UI;
case Tegra::RenderTargetFormat::BGRA8_UNORM:
return PixelFormat::BGRA8;
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
@@ -323,6 +335,8 @@ struct SurfaceParams {
return PixelFormat::ABGR8U;
case Tegra::Texture::ComponentType::SNORM:
return PixelFormat::ABGR8S;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::ABGR8UI;
}
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
static_cast<u32>(component_type));
@@ -372,7 +386,15 @@ struct SurfaceParams {
static_cast<u32>(component_type));
UNREACHABLE();
case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
return PixelFormat::RGBA16F;
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::RGBA16U;
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::RGBA16F;
}
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
static_cast<u32>(component_type));
UNREACHABLE();
case Tegra::Texture::TextureFormat::BF10GF11RF11:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
@@ -539,6 +561,7 @@ struct SurfaceParams {
case Tegra::RenderTargetFormat::R16_UINT:
case Tegra::RenderTargetFormat::RG32_UINT:
case Tegra::RenderTargetFormat::R32_UINT:
case Tegra::RenderTargetFormat::RGBA8_UINT:
return ComponentType::UInt;
case Tegra::RenderTargetFormat::RG16_SINT:
case Tegra::RenderTargetFormat::R16_SINT:
@@ -579,6 +602,10 @@ struct SurfaceParams {
return SurfaceType::ColorTexture;
}
if (static_cast<size_t>(pixel_format) < static_cast<size_t>(PixelFormat::MaxDepthFormat)) {
return SurfaceType::Depth;
}
if (static_cast<size_t>(pixel_format) <
static_cast<size_t>(PixelFormat::MaxDepthStencilFormat)) {
return SurfaceType::DepthStencil;
@@ -656,6 +683,27 @@ struct SurfaceParams {
u32 cache_height;
};
}; // namespace OpenGL
/// Hashable variation of SurfaceParams, used for a key in the surface cache
struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
SurfaceReserveKey res;
res.state = params;
return res;
}
};
namespace std {
template <>
struct hash<SurfaceReserveKey> {
size_t operator()(const SurfaceReserveKey& k) const {
return k.Hash();
}
};
} // namespace std
namespace OpenGL {
class CachedSurface final {
public:
CachedSurface(const SurfaceParams& params);
@@ -698,7 +746,8 @@ public:
Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config);
/// Get the color and depth surfaces based on the framebuffer configuration
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb);
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
bool preserve_contents);
/// Flushes the surface to Switch memory
void FlushSurface(const Surface& surface);
@@ -714,7 +763,7 @@ public:
private:
void LoadSurface(const Surface& surface);
Surface GetSurface(const SurfaceParams& params);
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
/// Recreates a surface with new parameters
Surface RecreateSurface(const Surface& surface, const SurfaceParams& new_params);
@@ -725,12 +774,25 @@ private:
/// Remove surface from the cache
void UnregisterSurface(const Surface& surface);
/// Reserves a unique surface that can be reused later
void ReserveSurface(const Surface& surface);
/// Tries to get a reserved surface for the specified parameters
Surface TryGetReservedSurface(const SurfaceParams& params);
/// Increase/decrease the number of surface in pages touching the specified region
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta);
std::unordered_map<Tegra::GPUVAddr, Surface> surface_cache;
PageMap cached_pages;
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
/// previously been used. This is to prevent surfaces from being constantly created and
/// destroyed when used with different surface parameters.
std::unordered_map<SurfaceReserveKey, Surface> surface_reserve;
OGLFramebuffer read_framebuffer;
OGLFramebuffer draw_framebuffer;
};
} // namespace OpenGL

View File

@@ -10,6 +10,8 @@
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state.h"
namespace OpenGL {
class OGLTexture : private NonCopyable {
public:
OGLTexture() = default;
@@ -331,3 +333,5 @@ public:
GLuint handle = 0;
};
} // namespace OpenGL

View File

@@ -15,7 +15,7 @@
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
namespace GLShader::Decompiler {
namespace OpenGL::GLShader::Decompiler {
using Tegra::Shader::Attribute;
using Tegra::Shader::Instruction;
@@ -26,6 +26,7 @@ using Tegra::Shader::Sampler;
using Tegra::Shader::SubOp;
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
constexpr u32 PROGRAM_HEADER_SIZE = 0x50;
class DecompileFail : public std::runtime_error {
public:
@@ -367,20 +368,23 @@ public:
}
/// Generates code representing a uniform (C buffer) register, interpreted as the input type.
std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) {
std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type,
Register::Size size = Register::Size::Word) {
declr_const_buffers[index].MarkAsUsed(index, offset, stage);
std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset / 4) + "][" +
std::to_string(offset % 4) + ']';
if (type == GLSLRegister::Type::Float) {
return value;
// Do nothing, default
} else if (type == GLSLRegister::Type::Integer) {
return "floatBitsToInt(" + value + ')';
value = "floatBitsToInt(" + value + ')';
} else if (type == GLSLRegister::Type::UnsignedInteger) {
return "floatBitsToUint(" + value + ')';
value = "floatBitsToUint(" + value + ')';
} else {
UNREACHABLE();
}
return ConvertIntegerSize(value, size);
}
std::string GetUniformIndirect(u64 cbuf_index, s64 offset, const std::string& index_str,
@@ -538,7 +542,11 @@ private:
// vertex shader, and what's the value of the fourth element when inside a Tess Eval
// shader.
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex);
return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))";
return "vec4(0, 0, uintBitsToFloat(instance_id.x), uintBitsToFloat(gl_VertexID))";
case Attribute::Index::FrontFacing:
// TODO(Subv): Find out what the values are for the other elements.
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment);
return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))";
default:
const u32 index{static_cast<u32>(attribute) -
static_cast<u32>(Attribute::Index::Attribute_0)};
@@ -613,6 +621,23 @@ public:
}
private:
// Shader program header for a Fragment Shader.
struct FragmentHeader {
INSERT_PADDING_WORDS(5);
INSERT_PADDING_WORDS(13);
u32 enabled_color_outputs;
union {
BitField<0, 1, u32> writes_samplemask;
BitField<1, 1, u32> writes_depth;
};
bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const {
u32 bit = render_target * 4 + component;
return enabled_color_outputs & (1 << bit);
}
};
static_assert(sizeof(FragmentHeader) == PROGRAM_HEADER_SIZE, "FragmentHeader size is wrong");
/// Gets the Subroutine object corresponding to the specified address.
const Subroutine& GetSubroutine(u32 begin, u32 end) const {
auto iter = subroutines.find(Subroutine{begin, end, suffix});
@@ -700,10 +725,11 @@ private:
const std::string& op_a, const std::string& op_b) const {
using Tegra::Shader::PredCondition;
static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = {
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
{PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="},
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
{PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="},
{PredCondition::GreaterThanWithNan, ">"},
};
const auto& comparison{PredicateComparisonStrings.find(condition)};
@@ -712,7 +738,8 @@ private:
std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'};
if (condition == PredCondition::LessThanWithNan ||
condition == PredCondition::NotEqualWithNan) {
condition == PredCondition::NotEqualWithNan ||
condition == PredCondition::GreaterThanWithNan) {
predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')';
}
@@ -738,6 +765,30 @@ private:
return op->second;
}
/**
* Transforms the input string GLSL operand into one that applies the abs() function and negates
* the output if necessary. When both abs and neg are true, the negation will be applied after
* taking the absolute value.
* @param operand The input operand to take the abs() of, negate, or both.
* @param abs Whether to apply the abs() function to the input operand.
* @param neg Whether to negate the input operand.
* @returns String corresponding to the operand after being transformed by the abs() and
* negation operations.
*/
static std::string GetOperandAbsNeg(const std::string& operand, bool abs, bool neg) {
std::string result = operand;
if (abs) {
result = "abs(" + result + ')';
}
if (neg) {
result = "-(" + result + ')';
}
return result;
}
/*
* Returns whether the instruction at the specified offset is a 'sched' instruction.
* Sched instructions always appear before a sequence of 3 instructions.
@@ -751,28 +802,78 @@ private:
}
void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a,
const std::string& op_b) {
const std::string& op_b,
Tegra::Shader::PredicateResultMode predicate_mode,
Tegra::Shader::Pred predicate) {
std::string result{};
switch (logic_op) {
case LogicOperation::And: {
regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1);
result = '(' + op_a + " & " + op_b + ')';
break;
}
case LogicOperation::Or: {
regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1);
result = '(' + op_a + " | " + op_b + ')';
break;
}
case LogicOperation::Xor: {
regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1);
result = '(' + op_a + " ^ " + op_b + ')';
break;
}
case LogicOperation::PassB: {
regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1);
result = op_b;
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op));
UNREACHABLE();
}
if (dest != Tegra::Shader::Register::ZeroIndex) {
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
}
using Tegra::Shader::PredicateResultMode;
// Write the predicate value depending on the predicate mode.
switch (predicate_mode) {
case PredicateResultMode::None:
// Do nothing.
return;
case PredicateResultMode::NotZero:
// Set the predicate to true if the result is not zero.
SetPredicate(static_cast<u64>(predicate), '(' + result + ") != 0");
break;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented predicate result mode: {}",
static_cast<u32>(predicate_mode));
UNREACHABLE();
}
}
void WriteLop3Instruction(Register dest, const std::string& op_a, const std::string& op_b,
const std::string& op_c, const std::string& imm_lut) {
if (dest == Tegra::Shader::Register::ZeroIndex) {
return;
}
static constexpr std::array<const char*, 32> shift_amounts = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21",
"22", "23", "24", "25", "26", "27", "28", "29", "30", "31"};
std::string result;
result += '(';
for (size_t i = 0; i < shift_amounts.size(); ++i) {
if (i)
result += '|';
result += "(((" + imm_lut + " >> (((" + op_c + " >> " + shift_amounts[i] +
") & 1) | ((" + op_b + " >> " + shift_amounts[i] + ") & 1) << 1 | ((" + op_a +
" >> " + shift_amounts[i] + ") & 1) << 2)) & 1) << " + shift_amounts[i] + ")";
}
result += ')';
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
}
void WriteTexsInstruction(const Instruction& instr, const std::string& coord,
@@ -783,33 +884,90 @@ private:
++shader.scope;
shader.AddLine(coord);
// TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA
// goes into gpr28+0 and gpr28+1
size_t texs_offset{};
// TEXS has two destination registers and a swizzle. The first two elements in the swizzle
// go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
size_t src_elem{};
for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) {
size_t dest_elem{};
for (unsigned elem = 0; elem < 2; ++elem) {
if (!instr.texs.IsComponentEnabled(src_elem++)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(dest, elem + texs_offset, texture, 1, 4, false,
dest_elem++);
size_t written_components = 0;
for (u32 component = 0; component < 4; ++component) {
if (!instr.texs.IsComponentEnabled(component)) {
continue;
}
if (!instr.texs.HasTwoDestinations()) {
// Skip the second destination
break;
if (written_components < 2) {
// Write the first two swizzle components to gpr0 and gpr0+1
regs.SetRegisterToFloat(instr.gpr0, component, texture, 1, 4, false,
written_components % 2);
} else {
ASSERT(instr.texs.HasTwoDestinations());
// Write the rest of the swizzle components to gpr28 and gpr28+1
regs.SetRegisterToFloat(instr.gpr28, component, texture, 1, 4, false,
written_components % 2);
}
texs_offset += 2;
++written_components;
}
--shader.scope;
shader.AddLine('}');
}
/*
* Emits code to push the input target address to the SSY address stack, incrementing the stack
* top.
*/
void EmitPushToSSYStack(u32 target) {
shader.AddLine('{');
++shader.scope;
shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;");
shader.AddLine("ssy_stack_top++;");
--shader.scope;
shader.AddLine('}');
}
/*
* Emits code to pop an address from the SSY address stack, setting the jump address to the
* popped address and decrementing the stack top.
*/
void EmitPopFromSSYStack() {
shader.AddLine('{');
++shader.scope;
shader.AddLine("ssy_stack_top--;");
shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];");
shader.AddLine("break;");
--shader.scope;
shader.AddLine('}');
}
/// Writes the output values from a fragment shader to the corresponding GLSL output variables.
void EmitFragmentOutputsWrite() {
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment);
FragmentHeader header;
std::memcpy(&header, program_code.data(), PROGRAM_HEADER_SIZE);
ASSERT_MSG(header.writes_samplemask == 0, "Samplemask write is unimplemented");
// Write the color outputs using the data in the shader registers, disabled
// rendertargets/components are skipped in the register assignment.
u32 current_reg = 0;
for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets;
++render_target) {
// TODO(Subv): Figure out how dual-source blending is configured in the Switch.
for (u32 component = 0; component < 4; ++component) {
if (header.IsColorComponentOutputEnabled(render_target, component)) {
shader.AddLine(fmt::format("color[{}][{}] = {};", render_target, component,
regs.GetRegisterAsFloat(current_reg)));
++current_reg;
}
}
}
if (header.writes_depth) {
// The depth output is always 2 registers after the last color output, and current_reg
// already contains one past the last color register.
shader.AddLine("gl_FragDepth = " + regs.GetRegisterAsFloat(current_reg + 1) + ';');
}
}
/**
* Compiles a single instruction from Tegra to GLSL.
* @param offset the offset of the Tegra shader instruction.
@@ -854,13 +1012,6 @@ private:
switch (opcode->GetType()) {
case OpCode::Type::Arithmetic: {
std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
if (instr.alu.abs_a) {
op_a = "abs(" + op_a + ')';
}
if (instr.alu.negate_a) {
op_a = "-(" + op_a + ')';
}
std::string op_b;
@@ -875,17 +1026,10 @@ private:
}
}
if (instr.alu.abs_b) {
op_b = "abs(" + op_b + ')';
}
if (instr.alu.negate_b) {
op_b = "-(" + op_b + ')';
}
switch (opcode->GetId()) {
case OpCode::Id::MOV_C:
case OpCode::Id::MOV_R: {
// MOV does not have neither 'abs' nor 'neg' bits.
regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1);
break;
}
@@ -893,6 +1037,8 @@ private:
case OpCode::Id::FMUL_C:
case OpCode::Id::FMUL_R:
case OpCode::Id::FMUL_IMM: {
// FMUL does not have 'abs' bits and only the second operand has a 'neg' bit.
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
instr.alu.saturate_d);
break;
@@ -900,11 +1046,14 @@ private:
case OpCode::Id::FADD_C:
case OpCode::Id::FADD_R:
case OpCode::Id::FADD_IMM: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1,
instr.alu.saturate_d);
break;
}
case OpCode::Id::MUFU: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
switch (instr.sub_op) {
case SubOp::Cos:
regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1,
@@ -944,6 +1093,9 @@ private:
case OpCode::Id::FMNMX_C:
case OpCode::Id::FMNMX_R:
case OpCode::Id::FMNMX_IMM: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
std::string condition =
GetPredicateCondition(instr.alu.fmnmx.pred, instr.alu.fmnmx.negate_pred != 0);
std::string parameters = op_a + ',' + op_b;
@@ -957,7 +1109,7 @@ private:
case OpCode::Id::RRO_R:
case OpCode::Id::RRO_IMM: {
// Currently RRO is only implemented as a register move.
// Usage of `abs_b` and `negate_b` here should also be correct.
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1);
LOG_WARNING(HW_GPU, "RRO instruction is incomplete");
break;
@@ -1094,7 +1246,9 @@ private:
if (instr.alu.lop32i.invert_b)
op_b = "~(" + op_b + ')';
WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b);
WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b,
Tegra::Shader::PredicateResultMode::None,
Tegra::Shader::Pred::UnusedIndex);
break;
}
default: {
@@ -1160,16 +1314,28 @@ private:
case OpCode::Id::LOP_C:
case OpCode::Id::LOP_R:
case OpCode::Id::LOP_IMM: {
ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented");
ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented");
if (instr.alu.lop.invert_a)
op_a = "~(" + op_a + ')';
if (instr.alu.lop.invert_b)
op_b = "~(" + op_b + ')';
WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b);
WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b,
instr.alu.lop.pred_result_mode, instr.alu.lop.pred48);
break;
}
case OpCode::Id::LOP3_C:
case OpCode::Id::LOP3_R:
case OpCode::Id::LOP3_IMM: {
std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
std::string lut;
if (opcode->GetId() == OpCode::Id::LOP3_R) {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
} else {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')';
}
WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut);
break;
}
case OpCode::Id::IMNMX_C:
@@ -1234,8 +1400,6 @@ private:
break;
}
case OpCode::Type::Conversion: {
ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented");
switch (opcode->GetId()) {
case OpCode::Id::I2I_R: {
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
@@ -1247,20 +1411,41 @@ private:
op_a = "abs(" + op_a + ')';
}
if (instr.conversion.negate_a) {
op_a = "-(" + op_a + ')';
}
regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,
1, instr.alu.saturate_d, 0, instr.conversion.dest_size);
break;
}
case OpCode::Id::I2F_R: {
case OpCode::Id::I2F_R:
case OpCode::Id::I2F_C: {
ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented");
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
std::string op_a = regs.GetRegisterAsInteger(
instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size);
std::string op_a{};
if (instr.is_b_gpr) {
op_a =
regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_input_signed,
instr.conversion.src_size);
} else {
op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
instr.conversion.is_input_signed
? GLSLRegister::Type::Integer
: GLSLRegister::Type::UnsignedInteger,
instr.conversion.src_size);
}
if (instr.conversion.abs_a) {
op_a = "abs(" + op_a + ')';
}
if (instr.conversion.negate_a) {
op_a = "-(" + op_a + ')';
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
break;
}
@@ -1269,6 +1454,14 @@ private:
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
if (instr.conversion.abs_a) {
op_a = "abs(" + op_a + ')';
}
if (instr.conversion.negate_a) {
op_a = "-(" + op_a + ')';
}
switch (instr.conversion.f2f.rounding) {
case Tegra::Shader::F2fRoundingOp::None:
break;
@@ -1291,19 +1484,27 @@ private:
break;
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
break;
}
case OpCode::Id::F2I_R:
case OpCode::Id::F2I_C: {
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
std::string op_a{};
if (instr.is_b_gpr) {
op_a = regs.GetRegisterAsFloat(instr.gpr20);
} else {
op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
GLSLRegister::Type::Float);
}
if (instr.conversion.abs_a) {
op_a = "abs(" + op_a + ')';
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
break;
}
case OpCode::Id::F2I_R: {
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
if (instr.conversion.abs_a) {
op_a = "abs(" + op_a + ')';
if (instr.conversion.negate_a) {
op_a = "-(" + op_a + ')';
}
switch (instr.conversion.f2i.rounding) {
@@ -1438,6 +1639,57 @@ private:
WriteTexsInstruction(instr, coord, texture);
break;
}
case OpCode::Id::TLD4: {
ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
ASSERT(instr.tld4.array == 0);
std::string coord{};
switch (instr.tld4.texture_type) {
case Tegra::Shader::TextureType::Texture2D: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
static_cast<u32>(instr.tld4.texture_type.Value()));
UNREACHABLE();
}
const std::string sampler = GetSampler(instr.sampler);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
shader.AddLine("{");
++shader.scope;
shader.AddLine(coord);
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4.component) + ')';
size_t dest_elem{};
for (size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
++dest_elem;
}
--shader.scope;
shader.AddLine("}");
break;
}
case OpCode::Id::TLD4S: {
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
const std::string sampler = GetSampler(instr.sampler);
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4s.component) + ')';
WriteTexsInstruction(instr, coord, texture);
break;
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
UNREACHABLE();
@@ -1737,12 +1989,8 @@ private:
default: {
switch (opcode->GetId()) {
case OpCode::Id::EXIT: {
// Final color output is currently hardcoded to GPR0-3 for fragment shaders
if (stage == Maxwell3D::Regs::ShaderStage::Fragment) {
shader.AddLine("color.r = " + regs.GetRegisterAsFloat(0) + ';');
shader.AddLine("color.g = " + regs.GetRegisterAsFloat(1) + ';');
shader.AddLine("color.b = " + regs.GetRegisterAsFloat(2) + ';');
shader.AddLine("color.a = " + regs.GetRegisterAsFloat(3) + ';');
EmitFragmentOutputsWrite();
}
switch (instr.flow.cond) {
@@ -1801,13 +2049,13 @@ private:
ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported");
u32 target = offset + instr.bra.GetBranchTarget();
shader.AddLine("ssy_target = " + std::to_string(target) + "u;");
EmitPushToSSYStack(target);
break;
}
case OpCode::Id::SYNC: {
// The SYNC opcode jumps to the address previously set by the SSY opcode
ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always);
shader.AddLine("{ jmp_to = ssy_target; break; }");
EmitPopFromSSYStack();
break;
}
case OpCode::Id::DEPBAR: {
@@ -1878,7 +2126,13 @@ private:
} else {
labels.insert(subroutine.begin);
shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;");
shader.AddLine("uint ssy_target = 0u;");
// TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems
// unlikely that shaders will use 20 nested SSYs.
constexpr u32 SSY_STACK_SIZE = 20;
shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];");
shader.AddLine("uint ssy_stack_top = 0u;");
shader.AddLine("while (true) {");
++shader.scope;
@@ -1963,4 +2217,4 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
return boost::none;
}
} // namespace GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -12,7 +12,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
namespace GLShader::Decompiler {
namespace OpenGL::GLShader::Decompiler {
using Tegra::Engines::Maxwell3D;
@@ -22,4 +22,4 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
Maxwell3D::Regs::ShaderStage stage,
const std::string& suffix);
} // namespace GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -7,7 +7,7 @@
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
namespace GLShader {
namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D;
@@ -38,6 +38,7 @@ out vec4 position;
layout (std140) uniform vs_config {
vec4 viewport_flip;
uvec4 instance_id;
};
void main() {
@@ -86,10 +87,11 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSCo
.get_value_or({});
out += R"(
in vec4 position;
out vec4 color;
layout(location = 0) out vec4 color[8];
layout (std140) uniform fs_config {
vec4 viewport_flip;
uvec4 instance_id;
};
void main() {
@@ -101,4 +103,4 @@ void main() {
return {out, program.second};
}
} // namespace GLShader
} // namespace OpenGL::GLShader

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