Compare commits

...

136 Commits

Author SHA1 Message Date
Fernando Sahmkow
1b11383e5e Merge f5379ac07e into 78f2a6a9e1 2018-10-17 00:13:25 +00:00
FernandoS27
f5379ac07e Fix cubemaps to use a less hacky method of loading 2018-10-16 19:49:02 -04:00
FernandoS27
5a15af013d Clang format and other fixes 2018-10-16 18:17:50 -04:00
FernandoS27
01db162f81 Implement Reinterpret Surface, to accurately blit 3D textures 2018-10-16 18:14:06 -04:00
FernandoS27
7d5e6b84fc Implement GetInRange in the Rasterizer Cache 2018-10-16 18:11:06 -04:00
FernandoS27
ee536c3ac2 Implement 3D Textures 2018-10-16 18:11:04 -04:00
bunnei
43b9494a0f gl_rasterizer_cache: Use AccurateCopySurface for use_accurate_gpu_emulation. 2018-10-16 17:20:49 -04:00
bunnei
ee7c2dbf5a config: Rename use_accurate_framebuffers -> use_accurate_gpu_emulation.
- This will be used as a catch-all for slow-but-accurate GPU emulation paths.
2018-10-16 17:02:29 -04:00
bunnei
91602de7f2 rasterizer_cache: Refactor to support in-order flushing. 2018-10-16 16:51:53 -04:00
bunnei
0e59291310 gl_rasterizer_cache: Refactor to only call GetRegionEnd on surface creation. 2018-10-16 11:31:02 -04:00
bunnei
949d7832fa gl_rasterizer_cache: Only flush when use_accurate_framebuffers is enabled. 2018-10-16 11:31:02 -04:00
bunnei
5f79ba04bd gl_rasterizer_cache: Separate guest and host surface size managment. 2018-10-16 11:31:01 -04:00
bunnei
58be4dff79 gl_rasterizer_cache: Rename GetGLBytesPerPixel to GetBytesPerPixel.
- This does not really have anything to do with OpenGL.
2018-10-16 11:31:01 -04:00
bunnei
cf7b46c101 gl_rasterizer_cache: Remove unused FlushSurface method. 2018-10-16 11:31:01 -04:00
bunnei
3afdfd7bfa gl_rasterizer: Implement flushing. 2018-10-16 11:31:01 -04:00
bunnei
b4e29ccb81 gl_rasterizer_cache: Remove usage of Memory::Read/Write functions.
- These cannot be used within the cache, as they change cache state.
2018-10-16 11:31:00 -04:00
bunnei
4e9683e9d5 gl_rasterizer_cache: Clamp cached surface size to mapped GPU region size. 2018-10-16 11:31:00 -04:00
bunnei
37575eae65 memory_manager: Add a method for querying the end of a mapped GPU region. 2018-10-16 11:31:00 -04:00
bunnei
0be7e82289 rasterizer_cache: Reintroduce method for flushing. 2018-10-16 11:31:00 -04:00
bunnei
9b929e934b gl_rasterizer_cache: Reintroduce code for handling swizzle and flush to guest RAM. 2018-10-16 11:30:59 -04:00
bunnei
78f2a6a9e1 Merge pull request #1443 from DarkLordZach/lower-loader-logs-1
content_archive/patch_manager: Lower log levels to eliminate some unnecessary logs
2018-10-16 11:26:54 -04:00
David
92d8ad3770 Implement VI ConvertScalingMode (#1475)
* Implement VI ConvertScalingMode

* Fixed push enum

* Scale mode now uses Nintendo scale mode as an enum as well
2018-10-16 11:25:42 -04:00
bunnei
88b8383da2 Merge pull request #1502 from lioncash/unique
core: Convert shared_ptr instances into unique_ptr instances where applicable for System and Cpu
2018-10-16 11:21:42 -04:00
bunnei
59c1ca8b0c Merge pull request #1508 from lioncash/unique-reg
file_sys/registered_cache: Use unique_ptr and regular pointers instead of shared_ptrs where applicable
2018-10-16 11:21:13 -04:00
bunnei
d6e390bc5c Merge pull request #1507 from FearlessTobi/port-4327
Port citra-emu/citra#4327: "travis: Ignore binary files when checking for trailing whitespace"
2018-10-16 10:42:10 -04:00
Lioncash
39ae73b356 file_sys/registered_cache: Use unique_ptr and regular pointers instead of shared_ptrs where applicable
The data retrieved in these cases are ultimately chiefly owned by either
the RegisteredCache instance itself, or the filesystem factories. Both
these should live throughout the use of their contained data. If they
don't, it should be considered an interface/design issue, and using
shared_ptr instances here would mask that, as the data would always be
prolonged after the main owner's lifetime ended.

This makes the lifetime of the data explicit and makes it harder to
accidentally create cyclic references. It also makes the interface
slightly more flexible than the previous API, as a shared_ptr can be
created from a unique_ptr, but not the other way around, so this allows
for that use-case if it ever becomes necessary in some form.
2018-10-16 09:38:52 -04:00
Cameron Cawley
41674d20ac travis: Ignore binary files when checking for trailing whitespace 2018-10-16 14:48:37 +02:00
bunnei
548958bcaf Merge pull request #1473 from lioncash/cmake
web_service: Make linkage of web_service-related externals and the library private
2018-10-15 21:33:32 -04:00
bunnei
870c18b078 Merge pull request #1487 from lioncash/maybe-unused
yuzu/main: Apply the [[maybe_unused]] attribute to the parameter of SetDiscordEnabled
2018-10-15 21:33:14 -04:00
bunnei
89fe950d3c Merge pull request #1504 from lioncash/constant
file_sys/control_metadata: Get rid of magic constants
2018-10-15 21:32:13 -04:00
Lioncash
76fc8b59b2 file_sys/control_metadata: Get rid of magic constants
These are just the size of the data being passed in, so we can specify
that via the size() member function.
2018-10-15 20:11:44 -04:00
bunnei
9b21fbd1eb Merge pull request #1494 from DarkLordZach/aoc-signature-fixes
aoc: Fix various bugs in current AOC implementation
2018-10-15 18:34:02 -04:00
bunnei
50e6205c21 Merge pull request #1499 from lioncash/nro
nro/nso: Minor error handling changes
2018-10-15 17:48:36 -04:00
bunnei
7665411317 Merge pull request #1500 from DarkLordZach/key-derivation-6.0.0
crypto: Various crypto fixes for quickstart guide
2018-10-15 17:48:13 -04:00
Lioncash
bed872ed38 nso: Return an optional address from LoadModule
If a malformed NSO is attempted to be loaded, we shouldn't continue
onwards. We should be reporting an error and bailing out.
2018-10-15 17:02:11 -04:00
bunnei
123df8f7d7 Merge pull request #1503 from ReinUsesLisp/misc-vc
video_core: Minor style changes
2018-10-15 16:38:20 -04:00
ReinUsesLisp
936c36a514 shader_bytecode: Add Control Code enum 0xf
Control Code 0xf means to unconditionally execute the instruction. This
value is passed to most BRA, EXIT and SYNC instructions (among others)
but this may not always be the case.
2018-10-15 15:36:47 -03:00
ReinUsesLisp
b461342a84 gl_shader_decompiler: Fixup style inconsistencies 2018-10-15 15:35:26 -03:00
ReinUsesLisp
27916764b1 gl_rasterizer: Silence implicit cast warning in glBindBufferRange 2018-10-15 15:26:50 -03:00
Lioncash
5484742fda core_cpu: Make Cpu scheduler instances unique_ptrs instead of shared_ptrs 2018-10-15 14:15:56 -04:00
Lioncash
59f872a8e0 core: Make the live Cpu instances unique_ptrs instead of shared_ptrs
There's no need for shared ownership here, as the only owning class
instance of those Cpu instances is the System class itself. We can also
make the thread_to_cpu map use regular pointers instead of shared_ptrs,
given that the Cpu instances will always outlive the cases where they're
used with that map.
2018-10-15 14:15:56 -04:00
Lioncash
aeadbfa790 core: Make the exclusive monitor a unique_ptr instead of a shared_ptr
Like the barrier, this is owned entirely by the System and will always
outlive the encompassing state, so shared ownership semantics aren't
necessary here.
2018-10-15 14:15:50 -04:00
Lioncash
c34efbbd60 core: Make CPUBarrier a unique_ptr instead of a shared_ptr
This will always outlive the Cpu instances, since it's destroyed after
we destroy the Cpu instances on shutdown, so there's no need for shared
ownership semantics here.
2018-10-15 09:11:47 -04:00
Zach Hilman
720d36ca71 crypto: Various crypto fixes for quickstart guide 2018-10-14 21:57:52 -04:00
Lioncash
bb9cf8a127 nso: Make LoadModule take a VfsFile by const reference 2018-10-14 20:38:19 -04:00
Lioncash
0732786ddc nro: Make LoadNro take a VfsFile by const reference
This function doesn't need to care about ownership semantics, so we can
just pass it a reference to the file itself, rather than a
std::shared_ptr alias.
2018-10-14 20:24:18 -04:00
Zach Hilman
5737441374 aoc: Read DLC base title ID from RegisteredCache
Falls back to title ID + 0x1000, which is what HOS does.
2018-10-14 18:58:14 -04:00
bunnei
b3cca34f50 Merge pull request #1486 from lioncash/file
key_manager/partition_data_manager: Minor changes
2018-10-14 14:46:47 -04:00
bunnei
3203193a67 Merge pull request #1490 from lioncash/boot
yuzu/main: Simplify OnMenuLoadFile()
2018-10-14 14:44:49 -04:00
bunnei
14286f70f0 Merge pull request #1488 from Hexagon12/astc-types
video_core: Added ASTC 5x4; 8x5 types
2018-10-14 14:44:24 -04:00
bunnei
0d2ba0a320 Merge pull request #1491 from lioncash/reference
filesystem: Make CreateFactories() and InstallInterface() take a VfsFilesystem by reference
2018-10-14 14:42:57 -04:00
bunnei
b82bbfba77 Merge pull request #1480 from FernandoS27/neue-swizzle
Introduce 3D Swizzle seamlessly
2018-10-14 14:42:38 -04:00
bunnei
2f8ca32020 Merge pull request #1492 from lioncash/proc
svc: Implement svcGetProcessInfo
2018-10-14 14:37:58 -04:00
bunnei
b183ce4365 Merge pull request #1495 from ogniK5377/break-stop
Stop all threads on svcBreak
2018-10-14 14:31:35 -04:00
David Marcec
92fae7e1ab Stop all threads on svcBreak
This should help diagnose crashes easier and prevent many users thinking that a game is still running when in fact it's just an audio thread still running(this is typically not killed when svcBreak is hit since the game expects us to do this)
2018-10-14 18:14:51 +11:00
Zach Hilman
7e2096db8a aoc: Return size in ListAddOnContent 2018-10-13 22:52:54 -04:00
FernandoS27
331ce2942c Shorten the implementation of 3D swizzle to only 3 functions 2018-10-13 20:58:00 -04:00
Lioncash
1c7a7ed79b svc: Implement svcGetProcessInfo
A fairly basic service function, which only appears to currently support
retrieving the process state. This also alters the ProcessStatus enum to
contain all of the values that a kernel process seems to be able of
reporting with regards to state.
2018-10-13 17:00:43 -04:00
FernandoS27
1ff20d8538 Fix a Crash on Zelda BotW and Splatoon 2, and simplified LoadGLBuffer 2018-10-13 16:11:11 -04:00
FernandoS27
e0ca938b22 Propagate depth and depth_block on modules using decoders 2018-10-13 15:25:18 -04:00
FernandoS27
d4ae43f9c1 Remove old Swizzle algorithms and use 3d Swizzle 2018-10-13 15:25:17 -04:00
FernandoS27
4d959c6bdc Implement Precise 3D Swizzle 2018-10-13 15:25:16 -04:00
FernandoS27
736db284d2 Implement Fast 3D Swizzle 2018-10-13 15:25:15 -04:00
Lioncash
0149162dba filesystem: Make CreateFactories() and InstallInterface() take a VfsFilesystem instance by reference
Neither of these functions alter the ownership of the provided pointer,
so we can simply make the parameters a reference rather than a direct
shared pointer alias. This way we also disallow passing incorrect memory values like
nullptr.
2018-10-13 11:36:35 -04:00
Lioncash
a4c57436fc yuzu/main: Simplify OnMenuLoadFile()
We can utilize QStringList's join() function to perform all of the
appending in a single function call.

While we're at it, make the extension list a single translatable string
and add a disambiguation comment to explain to translators what %1
actually is.
2018-10-13 10:35:18 -04:00
Lioncash
53a0221484 yuzu/main: Apply the [[maybe_unused]] attribute to the parameter of SetDiscordEnabled()
Depending on whether or not USE_DISCORD_PRESENCE is defined, the "state"
parameter can be used or unused. If USE_DISCORD_PRESENCE is not defined,
the parameter will be considered unused, which can lead to compiler
warnings. So, we can explicitly mark it with [[maybe_unused]] to inform
the compiler that this is intentional.
2018-10-13 10:10:29 -04:00
Hexagon12
cbf723896f Added ASTC 5x4; 8x5 2018-10-13 17:10:26 +03:00
Lioncash
6467b01de2 partition_data_manager: Reserve and insert data within output vector in DecryptPackage2()
We can just reserve the memory then perform successive insertions
instead of needing to use memcpy. This also avoids the need to zero out
the output vector's memory before performing the insertions.

We can also std::move the output std::vector into the destination so
that we don't need to make a completely new copy of the vector, getting
rid of an unnecessary allocation.

Additionally, we can use iterators to determine the beginning and end
ranges of the std::vector instances that comprise the output vector, as
the end of one range just becomes the beginning for the next successive
range, and since std::vector's iterator constructor copies data within
the range [begin, end), this is more straightforward and gets rid of the
need to have an offset variable that keeps getting incremented to
determine where to do the next std::memcpy.
2018-10-13 09:50:08 -04:00
Lioncash
781fd7983c partition_data_manager: Remove unused std::map instance within DecryptPackage2()
Aside from emplacing elements into the map, the map itself is never
actually queried for contained data.
2018-10-13 09:27:12 -04:00
Lioncash
e0c76226ad partition_data_manager: Take package2_keys by const reference
These are only ever read from, so we don't need to make a copy of all
the keys here.
2018-10-13 09:24:41 -04:00
Lioncash
3d9df49619 partition_data_manager: Move IV data to where it's needed in DecryptPackage2()
Given it's only used in one spot and has a fairly generic name, we can
just specify it directly in the function call. This also the benefit of
automatically moving it.
2018-10-13 09:20:21 -04:00
Lioncash
bc2196bb09 partition_data_manager: Remove commented out code
Commented out code shouldn't be left in without a reason indicating why
in a comment.
2018-10-13 09:17:02 -04:00
Lioncash
6da2ed4232 key_manager/partition_data_manager: Silence truncation compiler warnings 2018-10-13 09:13:19 -04:00
Lioncash
f56a8da46a partition_data_manager: Dehardcode array bounds
Instead, we can make it part of the type and make named variables for
them, so they only require one definition (and if they ever change for
whatever reason, they only need to be changed in one spot).
2018-10-13 08:52:37 -04:00
Lioncash
d257a3b56c partition_data_manager: Take VirtualFile by const reference in constructor
Given the VirtualFile instance isn't stored into the class as a data
member, or written to, this can just be turned into a const reference,
as the constructor doesn't need to make a copy of it.
2018-10-13 08:39:05 -04:00
Lioncash
e96d69c328 partition_data_manager: Amend constructor initializer list order
Orders the members in the exact order they would be initialized. This
also prevents compiler warnings about this sort of thing.
2018-10-13 08:36:26 -04:00
Lioncash
aaca7543f0 partition_data_manager: Remove unused includes
Gets unused includes out of the headers and moves them into the cpp file
if they're used there instead.
2018-10-13 08:33:49 -04:00
Lioncash
06898263f6 key_manager: Use std::vector's insert() instead of std::copy with a back_inserter
If the data is unconditionally being appended to the back of a
std::vector, we can just directly insert it there without the need to
insert all of the elements one-by-one with a std::back_inserter.
2018-10-13 08:29:35 -04:00
Lioncash
e70c08b543 key_manager: Brace long conditional body
If a conditional (or it's body) travels more than one line, it should be
braced.
2018-10-13 08:24:21 -04:00
Lioncash
ef5639bfbb key_manager: Don't assume file seeks and reads will always succeed
Given the filesystem should always be assumed to be volatile, we should
check and bail out if a seek operation isn't successful. This'll prevent
potentially writing/returning garbage data from the function in rare
cases.

This also allows removing a check to see if an offset is within the
bounds of a file before perfoming a seek operation. If a seek is
attempted beyond the end of a file, it will fail, so this essentially
combines two checks into one in one place.
2018-10-13 08:24:18 -04:00
Lioncash
82ea1cf35a key_manager: Remove unnecessary seek in DeriveSDSeed()
Given the file is opened a few lines above and no operations are done,
other than check if the file is in a valid state, the read/write pointer
will always be at the beginning of the file.
2018-10-13 08:08:44 -04:00
Zach Hilman
f61379f8d2 patch_manager: Move non-Program RomFS patch log to Debug
Normal Program-type patches will still be logged to aid in debugging, but for others (mainly Control), it was moved to Debug.
2018-10-12 23:27:19 -04:00
Zach Hilman
90c07e0d33 content_archive: Move get key log to Trace level
Avoids printing live keys in the general log.
2018-10-12 23:25:59 -04:00
bunnei
1584fb6b38 Merge pull request #1409 from DarkLordZach/key-derivation
crypto: Add support for full key derivation
2018-10-12 22:55:49 -04:00
bunnei
c2aa4293ec Merge pull request #1483 from lioncash/codeset
kernel/process: Make CodeSet a regular non-inherited object
2018-10-12 22:52:12 -04:00
bunnei
38b027aa81 Merge pull request #1484 from FernandoS27/calculate-size
Implemented helper function to correctly calculate a texture's size
2018-10-12 21:12:53 -04:00
bunnei
ffcda6c08e Merge pull request #1481 from lioncash/typo
svc: Fix typos in sanitizing checks for MapMemory/UnmapMemory
2018-10-12 20:46:55 -04:00
FernandoS27
97b6405a17 Implemented helper function to correctly calculate a texture's size 2018-10-12 14:21:53 -04:00
bunnei
2946d4bdbe Merge pull request #1467 from ogniK5377/svcbreak-type-fix
Fixed incorrect types for svcBreak
2018-10-12 12:08:08 -04:00
Lioncash
1abed2f4c4 kernel/process: Make CodeSet a regular non-inherited object
These only exist to ferry data into a Process instance and end up going
out of scope quite early. Because of this, we can just make it a plain
struct for holding things and just std::move it into the relevant
function. There's no need to make this inherit from the kernel's Object
type.
2018-10-12 12:07:32 -04:00
bunnei
0f7ab3e21a Merge pull request #1478 from ogniK5377/remap-invalidhandle-remap
Passing an invalid nmap handle to Remap should throw an error
2018-10-12 12:07:14 -04:00
bunnei
f9d03b1d41 Merge pull request #1482 from lioncash/init
thread: Remove unnecessary memset from ResetThreadContext()
2018-10-12 12:06:51 -04:00
bunnei
dc328440c8 Merge pull request #1479 from ogniK5377/nmap-revamped
Added error codes for nvmap
2018-10-12 12:06:22 -04:00
Lioncash
b492d43e63 thread: Remove unnecessary memset from ResetThreadContext()
Regular value initialization is adequate here for zeroing out data. It
also has the benefit of not invoking undefined behavior if a non-trivial
type is ever added to the struct for whatever reason.
2018-10-12 10:57:31 -04:00
David Marcec
4d2de6564f Returned an error before processing other remaps 2018-10-12 17:10:41 +11:00
David Marcec
c55b5de0fb Made the minimum alignment more clear 2018-10-12 17:06:46 +11:00
Lioncash
4ccf30dfaa svc: Fix typos in sanitizing checks for MapMemory/UnmapMemory 2018-10-12 01:48:26 -04:00
bunnei
9bf409f275 Merge pull request #1474 from ogniK5377/hwopus-decodeinterleavedwithperformance
HwOpus, Implemented DecodeInterleavedWithPerformance
2018-10-11 16:52:13 -04:00
bunnei
3fd26b7147 Merge pull request #1472 from lioncash/san
svc: Add missing address range sanitizing checks to MapMemory/UnmapMemory
2018-10-11 16:51:41 -04:00
bunnei
bc293e1751 Merge pull request #1476 from bunnei/fix-unmap-flush
nvhost_as_gpu: Flush/invalidate CPU VAddr on UnmapBuffer.
2018-10-11 16:51:28 -04:00
bunnei
83ac3e6395 Merge pull request #1477 from ReinUsesLisp/vmad
gl_shader_decompiler: Implement VMAD
2018-10-11 16:51:09 -04:00
David Marcec
c7763603ef Added error codes for nvmap 2018-10-11 23:06:34 +11:00
David Marcec
5dd538cace Passing an invalid nmap handle to Remap should throw an error
Added error for invalid nmap handles
2018-10-11 20:32:21 +11:00
ReinUsesLisp
17290a4416 gl_shader_decompiler: Implement VMAD 2018-10-11 04:15:10 -03:00
bunnei
bf795edac4 nvhost_as_gpu: Flush CPU VAddr on UnmapBuffer. 2018-10-11 00:19:36 -04:00
Lioncash
28ec921d0d core/CMakeLists: Make all web_service-related libraries private
Now that all external dependencies are hidden, we can remove
json-headers from the publically linked libraries, as the use of this
library is now completely hidden from external users of the web_service
library. We can also make the web_services library private as well,
considering it's not a requirement. If a library needs to link in
web_service, it should be done explicitly -- not via indirect linking.
2018-10-10 22:29:39 -04:00
Lioncash
183a664405 web_backend: Make Client use the PImpl idiom
Like with TelemetryJson, we can make the implementation details private
and avoid the need to expose httplib to external libraries that need to
use the Client class.
2018-10-10 22:29:35 -04:00
David Marcec
fa10905e1e HwOpus, Implemented DecodeInterleavedWithPerformance
Used by sonic ages
2018-10-11 13:06:56 +11:00
bunnei
6d82c4adf9 Merge pull request #1458 from FernandoS27/fix-render-target-block-settings
Fixed block height settings for RenderTargets and Depth Buffers
2018-10-10 21:24:07 -04:00
Lioncash
a7725d354c telemetry_json: Use the PImpl idiom to avoid unnecessary dependency exposure
Users of the web_service library shouldn't need to care about an
external library like json.h. However, given it's exposed in our
interface, this requires that other libraries publicly link in the JSON
library. We can do better.

By using the PImpl idiom, we can hide this dependency in the cpp file
and remove the need to link that library in altogether.
2018-10-10 21:10:36 -04:00
Lioncash
c422f146ee telemetry_json: Add missing override specifier to the destructor of TelemetryJson 2018-10-10 21:00:39 -04:00
Lioncash
881bb2295d telemetry_json: Take std::string parameters by value
Taking them by const reference isn't advisable here, because it means
the std::move calls were doing nothing and we were always copying the
std::string instances.
2018-10-10 20:59:28 -04:00
Lioncash
a34e5e51d8 telemetry_json: Remove unnecessary includes
Removes unused includes. Also rectifies a missing <chrono> include.
2018-10-10 20:57:31 -04:00
Lioncash
6e6ce2ce39 core/CMakeLists: Use target_compile_definitions instead of add_definitions for specifying ENABLE_WEB_SERVICE
Avoids introducing the definition to the whole directory space and
localizes it to being added to the library that needs it.
2018-10-10 20:54:02 -04:00
Lioncash
72e9cb523e svc: Add missing address range sanitizing checks to MapMemory/UnmapMemory
This adds the missing address range checking that the service functions
do before attempting to map or unmap memory. Given that both service
functions perform the same set of checks in the same order, we can wrap
these into a function and just call it from both functions, which
deduplicates a little bit of code.
2018-10-10 20:30:49 -04:00
FernandoS27
5f4ee6f0c8 Add memory Layout to Render Targets and Depth Buffers 2018-10-09 22:28:19 -04:00
David Marcec
2db37ddea9 Changed all casts in svc_wrap.h to be static_cast instead 2018-10-10 12:49:08 +11:00
David Marcec
09b6dda8f0 Use a better name than "dont_kill_application"
signal_debugger seems like a more fitting name
2018-10-10 12:27:44 +11:00
David Marcec
a4412c8e22 Fixed incorrect types for svcBreak
svcBreak reason should be a u32, not a u64.
2018-10-10 12:23:50 +11:00
FernandoS27
af653906d0 Fixed block height settings for RenderTargets and Depth Buffers, and added block width and block depth 2018-10-09 21:14:32 -04:00
Zach Hilman
3ec054643e partition_data_manager: Rename system files for hekate
x
2018-10-07 13:16:23 -04:00
Zach Hilman
8f958b89e7 qt: Add rederive keyset menu option 2018-10-07 13:16:04 -04:00
Zach Hilman
3edafc6802 qt: Add key derivation progress bar on initial setup 2018-10-07 13:15:11 -04:00
Zach Hilman
29dc6f4519 crypto: Add PartitionDataManager
Keeps track of system files for key derivation
2018-10-07 13:15:11 -04:00
Zach Hilman
4aad010f7a key_manager: Add support for loading keys from partition data 2018-10-07 13:15:11 -04:00
Zach Hilman
d041d6231c key_manager: Add ETicket key derivation
Derives titlekeys
2018-10-07 13:15:11 -04:00
Zach Hilman
a57aac5772 key_manager: Add base key derivation
Derives master keys, game encryption keys, and package1/2 keys
2018-10-07 13:15:11 -04:00
Zach Hilman
d7398283e3 key_manager: Add BIS key getter 2018-10-07 13:15:11 -04:00
Zach Hilman
d6a0d5d432 key_manager: Add support for more keys
TSEC, SBK, BIS, and other Sources for proper derivation
2018-10-07 13:15:11 -04:00
Zach Hilman
c79d2ca6cf key_manager: Add keyblob support 2018-10-07 13:15:11 -04:00
Zach Hilman
e4602748d6 key_manager: Add support for crypto revisions past 04 2018-10-07 13:15:11 -04:00
Zach Hilman
9e34303fb9 key_manager: Add support for comments in keyfiles 2018-10-07 13:15:11 -04:00
Zach Hilman
1fa6ee4723 vfs: Move forward declarations to separate file 2018-10-07 13:15:11 -04:00
Zach Hilman
ce05df0a6d key_manager: Add support for console-specific keyfile 2018-10-07 13:15:11 -04:00
Zach Hilman
721632fe66 key_manager: Rename KEK to Kek 2018-10-07 13:15:11 -04:00
Zach Hilman
89ad82ce5c externals/mbedtls: Enable CMAC module
Required for keyblob verification
2018-10-07 13:15:11 -04:00
92 changed files with 3409 additions and 981 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/bash -ex
if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
dist/*.svg dist/*.xml; then
echo Trailing whitespace found, aborting
exit 1

View File

@@ -5,6 +5,7 @@
#pragma once
#include <string>
#include "common/common_types.h"
namespace Common {
struct WebResult {

View File

@@ -18,6 +18,8 @@ add_library(core STATIC
crypto/encryption_layer.h
crypto/key_manager.cpp
crypto/key_manager.h
crypto/partition_data_manager.cpp
crypto/partition_data_manager.h
crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
@@ -70,6 +72,7 @@ add_library(core STATIC
file_sys/vfs_real.cpp
file_sys/vfs_real.h
file_sys/vfs_static.h
file_sys/vfs_types.h
file_sys/vfs_vector.cpp
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
@@ -397,8 +400,8 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
add_definitions(-DENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
endif()
if (ARCHITECTURE_x86_64)

View File

@@ -144,7 +144,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
// Multi-process state
config.processor_id = core_index;
config.global_monitor = &exclusive_monitor->monitor;
config.global_monitor = &exclusive_monitor.monitor;
// System registers
config.tpidrro_el0 = &cb->tpidrro_el0;
@@ -171,10 +171,9 @@ void ARM_Dynarmic::Step() {
cb->InterpreterFallback(jit->GetPC(), 1);
}
ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
std::size_t core_index)
ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index)
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
ThreadContext ctx{};
inner_unicorn.SaveContext(ctx);
PageTableChanged();

View File

@@ -23,7 +23,7 @@ class DynarmicExclusiveMonitor;
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index);
ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic();
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
@@ -62,7 +62,7 @@ private:
ARM_Unicorn inner_unicorn;
std::size_t core_index;
std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor;
DynarmicExclusiveMonitor& exclusive_monitor;
Memory::PageTable* current_page_table = nullptr;
};

View File

@@ -71,9 +71,9 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
}
/// Runs a CPU core while the system is powered on
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
void RunCpuCore(Cpu& cpu_state) {
while (Core::System::GetInstance().IsPoweredOn()) {
cpu_state->RunLoop(true);
cpu_state.RunLoop(true);
}
}
} // Anonymous namespace
@@ -95,7 +95,7 @@ struct System::Impl {
status = ResultStatus::Success;
// Update thread_to_cpu in case Core 0 is run from a different host thread
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
@@ -139,16 +139,16 @@ struct System::Impl {
auto main_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(main_process.get());
cpu_barrier = std::make_shared<CpuBarrier>();
cpu_barrier = std::make_unique<CpuBarrier>();
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
for (std::size_t index = 0; index < cpu_cores.size(); ++index) {
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index);
}
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
Service::Init(service_manager, virtual_filesystem);
Service::Init(service_manager, *virtual_filesystem);
GDBStub::Init();
renderer = VideoCore::CreateRenderer(emu_window);
@@ -160,12 +160,12 @@ struct System::Impl {
// Create threads for CPU cores 1-3, and build thread_to_cpu map
// CPU core 0 is run on the main thread
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
if (Settings::values.use_multi_core) {
for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) {
cpu_core_threads[index] =
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1]));
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get();
}
}
@@ -245,6 +245,7 @@ struct System::Impl {
for (auto& cpu_core : cpu_cores) {
cpu_core.reset();
}
cpu_exclusive_monitor.reset();
cpu_barrier.reset();
// Shutdown kernel and core timing
@@ -282,9 +283,9 @@ struct System::Impl {
std::unique_ptr<VideoCore::RendererBase> renderer;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
std::shared_ptr<CpuBarrier> cpu_barrier;
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
std::unique_ptr<CpuBarrier> cpu_barrier;
std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
std::size_t active_core{}; ///< Active core, only used in single thread mode
@@ -298,7 +299,7 @@ struct System::Impl {
std::string status_details = "";
/// Map of guest threads to CPU cores
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
std::map<std::thread::id, Cpu*> thread_to_cpu;
Core::PerfStats perf_stats;
Core::FrameLimiter frame_limiter;
@@ -354,12 +355,15 @@ std::size_t System::CurrentCoreIndex() {
}
Kernel::Scheduler& System::CurrentScheduler() {
return *CurrentCpuCore().Scheduler();
return CurrentCpuCore().Scheduler();
}
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) {
ASSERT(core_index < NUM_CPU_CORES);
return impl->cpu_cores[core_index]->Scheduler();
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
return CpuCore(core_index).Scheduler();
}
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
return CpuCore(core_index).Scheduler();
}
Kernel::Process* System::CurrentProcess() {
@@ -380,6 +384,11 @@ Cpu& System::CpuCore(std::size_t core_index) {
return *impl->cpu_cores[core_index];
}
const Cpu& System::CpuCore(std::size_t core_index) const {
ASSERT(core_index < NUM_CPU_CORES);
return *impl->cpu_cores[core_index];
}
ExclusiveMonitor& System::Monitor() {
return *impl->cpu_exclusive_monitor;
}

View File

@@ -156,6 +156,9 @@ public:
/// Gets a CPU interface to the CPU core with the specified index
Cpu& CpuCore(std::size_t core_index);
/// Gets a CPU interface to the CPU core with the specified index
const Cpu& CpuCore(std::size_t core_index) const;
/// Gets the exclusive monitor
ExclusiveMonitor& Monitor();
@@ -172,7 +175,10 @@ public:
const VideoCore::RendererBase& Renderer() const;
/// Gets the scheduler for the CPU core with the specified index
const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
Kernel::Scheduler& Scheduler(std::size_t core_index);
/// Gets the scheduler for the CPU core with the specified index
const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
/// Provides a pointer to the current process
Kernel::Process* CurrentProcess();

View File

@@ -49,10 +49,8 @@ bool CpuBarrier::Rendezvous() {
return false;
}
Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index)
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index)
: cpu_barrier{cpu_barrier}, core_index{core_index} {
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
@@ -64,15 +62,15 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
arm_interface = std::make_unique<ARM_Unicorn>();
}
scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
}
Cpu::~Cpu() = default;
std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
return std::make_shared<DynarmicExclusiveMonitor>(num_cores);
return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
#else
return nullptr; // TODO(merry): Passthrough exclusive monitor
#endif
@@ -83,7 +81,7 @@ std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_core
void Cpu::RunLoop(bool tight_loop) {
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
if (!cpu_barrier->Rendezvous()) {
if (!cpu_barrier.Rendezvous()) {
// If rendezvous failed, session has been killed
return;
}

View File

@@ -41,8 +41,7 @@ private:
class Cpu {
public:
Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index);
Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index);
~Cpu();
void RunLoop(bool tight_loop = true);
@@ -59,8 +58,12 @@ public:
return *arm_interface;
}
const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
return scheduler;
Kernel::Scheduler& Scheduler() {
return *scheduler;
}
const Kernel::Scheduler& Scheduler() const {
return *scheduler;
}
bool IsMainCore() const {
@@ -71,14 +74,14 @@ public:
return core_index;
}
static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
private:
void Reschedule();
std::unique_ptr<ARM_Interface> arm_interface;
std::shared_ptr<CpuBarrier> cpu_barrier;
std::shared_ptr<Kernel::Scheduler> scheduler;
CpuBarrier& cpu_barrier;
std::unique_ptr<Kernel::Scheduler> scheduler;
std::atomic<bool> reschedule_pending = false;
std::size_t core_index;

View File

@@ -4,23 +4,56 @@
#include <algorithm>
#include <array>
#include <bitset>
#include <cctype>
#include <fstream>
#include <locale>
#include <map>
#include <sstream>
#include <string_view>
#include <tuple>
#include <vector>
#include <mbedtls/bignum.h>
#include <mbedtls/cipher.h>
#include <mbedtls/cmac.h>
#include <mbedtls/sha256.h>
#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Core::Crypto {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
using namespace Common;
const std::array<SHA256Hash, 2> eticket_source_hashes{
"B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
"E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
};
const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::Master, 0}, "master_key_"},
{{S128KeyType::Package1, 0}, "package1_key_"},
{{S128KeyType::Package2, 0}, "package2_key_"},
{{S128KeyType::Titlekek, 0}, "titlekek_"},
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -37,57 +70,136 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
return out;
}
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) {
AESCipher<Key128> sbk_cipher(sbk, Mode::ECB);
AESCipher<Key128> tsec_cipher(tsec, Mode::ECB);
tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
return source;
}
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) {
Key128 master_root;
std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128));
AESCipher<Key128> master_cipher(master_root, Mode::ECB);
Key128 master{};
master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt);
return master;
}
std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob,
const Key128& key) {
std::array<u8, 0x90> keyblob;
AESCipher<Key128> cipher(key, Mode::CTR);
cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20));
cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt);
return keyblob;
}
void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
const auto kek_generation_source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
const auto key_generation_source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
if (HasKey(S128KeyType::Master, crypto_revision)) {
for (auto kak_type :
{KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) {
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(kak_type))) {
const auto source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(kak_type));
const auto kek =
GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision),
kek_generation_source, key_generation_source);
SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type));
}
}
AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB);
for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) {
if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) {
Key128 key{};
master_cipher.Transcode(
GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(),
key.data(), Op::Decrypt);
SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek
: S128KeyType::Package2,
key, crypto_revision);
}
}
}
}
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
Key128 mac_key{};
mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt);
return mac_key;
}
boost::optional<Key128> DeriveSDSeed() {
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000043",
"rb+");
if (!save_43.IsOpen())
return boost::none;
const FileUtil::IOFile sd_private(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
if (!sd_private.IsOpen())
return boost::none;
sd_private.Seek(0, SEEK_SET);
std::array<u8, 0x10> private_seed{};
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
return boost::none;
}
std::array<u8, 0x10> buffer{};
std::size_t offset = 0;
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
save_43.Seek(offset, SEEK_SET);
if (!save_43.Seek(offset, SEEK_SET)) {
return boost::none;
}
save_43.ReadBytes(buffer.data(), buffer.size());
if (buffer == private_seed)
if (buffer == private_seed) {
break;
}
}
if (offset + 0x10 >= save_43.GetSize())
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
return boost::none;
}
Key128 seed{};
save_43.Seek(offset + 0x10, SEEK_SET);
save_43.ReadBytes(seed.data(), seed.size());
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
return boost::none;
}
return seed;
}
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
return Loader::ResultStatus::ErrorMissingSDKEKSource;
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
const auto sd_kek_source =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
const auto aes_kek_gen =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
const auto aes_key_gen =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
const auto master_00 = keys.GetKey(S128KeyType::Master);
const auto sd_kek =
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
keys.SetKey(S128KeyType::SDKek, sd_kek);
if (!keys.HasKey(S128KeyType::SDSeed))
return Loader::ResultStatus::ErrorMissingSDSeed;
@@ -118,9 +230,147 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag
return source; ///< Return unaltered source to satisfy output requirement.
});
keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save));
keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA));
return Loader::ResultStatus::Success;
}
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
if (!ticket_save.IsOpen())
return {};
std::vector<u8> buffer(ticket_save.GetSize());
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
return {};
}
std::vector<TicketRaw> out;
u32 magic{};
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
out.emplace_back();
auto& next = out.back();
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
offset += next.size();
}
}
return out;
}
template <size_t size>
static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
const std::array<u8, size>& rhs) {
std::array<u8, size> out{};
std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>());
return out;
}
template <size_t target_size, size_t in_size>
static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
// Avoids truncation overflow within the loop below.
static_assert(target_size <= 0xFF);
std::array<u8, in_size + 4> seed_exp{};
std::memcpy(seed_exp.data(), seed.data(), in_size);
std::vector<u8> out;
size_t i = 0;
while (out.size() < target_size) {
out.resize(out.size() + 0x20);
seed_exp[in_size + 3] = static_cast<u8>(i);
mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
++i;
}
std::array<u8, target_size> target;
std::memcpy(target.data(), out.data(), target_size);
return target;
}
template <size_t size>
static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
u64 offset = 0;
for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
if (data[i] == 0x1) {
offset = i + 1;
break;
} else if (data[i] != 0x0) {
return boost::none;
}
}
return offset;
}
boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
const RSAKeyPair<2048>& key) {
u32 cert_authority;
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
if (cert_authority == 0)
return boost::none;
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
LOG_INFO(Crypto,
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
cert_authority);
}
Key128 rights_id;
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
if (rights_id == Key128{})
return boost::none;
Key128 key_temp{};
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
return std::make_pair(rights_id, key_temp);
}
mbedtls_mpi D; // RSA Private Exponent
mbedtls_mpi N; // RSA Modulus
mbedtls_mpi S; // Input
mbedtls_mpi M; // Output
mbedtls_mpi_init(&D);
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&M);
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
std::array<u8, 0x100> rsa_step;
mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
u8 m_0 = rsa_step[0];
std::array<u8, 0x20> m_1;
std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
std::array<u8, 0xDF> m_2;
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
if (m_0 != 0)
return boost::none;
m_1 = m_1 ^ MGF1<0x20>(m_2);
m_2 = m_2 ^ MGF1<0xDF>(m_1);
const auto offset = FindTicketOffset(m_2);
if (offset == boost::none)
return boost::none;
ASSERT(offset.get() > 0);
std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
return std::make_pair(rights_id, key_temp);
}
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -137,6 +387,15 @@ KeyManager::KeyManager() {
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
}
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
if (base.size() < begin + length)
return false;
return std::all_of(base.begin() + begin, base.begin() + begin + length,
[](u8 c) { return std::isdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
@@ -158,6 +417,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
if (out[0].compare(0, 1, "#") == 0)
continue;
if (is_title_keys) {
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
u128 rights_id{};
@@ -174,6 +436,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
const auto index = s256_file_id.at(out[0]);
Key256 key = Common::HexStringToArray<32>(out[1]);
s256_keys[{index.type, index.field1, index.field2}] = key;
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
out[0].compare(0, 9, "keyblob_k") != 0) {
if (!ValidCryptoRevisionString(out[0], 8, 2))
continue;
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
if (!ValidCryptoRevisionString(out[0], 18, 2))
continue;
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
continue;
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
const auto sub = kv.first.second;
if (sub == 0) {
s128_keys[{kv.first.first, index, 0}] =
Common::HexStringToArray<16>(out[1]);
} else {
s128_keys[{kv.first.first, kv.first.second, index}] =
Common::HexStringToArray<16>(out[1]);
}
break;
}
}
static constexpr std::array<const char*, 3> kak_names = {
"key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"};
for (size_t j = 0; j < kak_names.size(); ++j) {
const auto& match = kak_names[j];
if (out[0].compare(0, std::strlen(match), match) == 0) {
const auto index =
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
s128_keys[{S128KeyType::KeyArea, index, j}] =
Common::HexStringToArray<16>(out[1]);
}
}
}
}
}
@@ -187,6 +493,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&
LoadFromFile(dir2 + DIR_SEP + filename, title);
}
bool KeyManager::BaseDeriveNecessary() const {
const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
return !HasKey(key_type, index1, index2);
};
if (check_key_existence(S256KeyType::Header))
return true;
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
if (check_key_existence(S128KeyType::Master, i) ||
check_key_existence(S128KeyType::KeyArea, i,
static_cast<u64>(KeyAreaKeyType::Application)) ||
check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) ||
check_key_existence(S128KeyType::KeyArea, i,
static_cast<u64>(KeyAreaKeyType::System)) ||
check_key_existence(S128KeyType::Titlekek, i))
return true;
}
return false;
}
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
return s128_keys.find({id, field1, field2}) != s128_keys.end();
}
@@ -207,13 +535,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
return s256_keys.at({id, field1, field2});
}
template <std::size_t Size>
void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
Key256 KeyManager::GetBISKey(u8 partition_id) const {
Key256 out{};
for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) {
if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) {
std::memcpy(
out.data() + sizeof(Key128) * static_cast<u64>(bis_type),
s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(),
sizeof(Key128));
}
}
return out;
}
template <size_t Size>
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key) {
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
std::string filename = "title.keys_autogenerated";
if (!title_key)
if (category == KeyCategory::Standard)
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
else if (category == KeyCategory::Console)
filename = "console.keys_autogenerated";
const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
@@ -227,7 +572,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
}
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -237,8 +582,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
}
auto category = KeyCategory::Standard;
if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
category = KeyCategory::Console;
}
const auto iter2 = std::find_if(
s128_file_id.begin(), s128_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
@@ -246,7 +598,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter2 != s128_file_id.end())
WriteKeyToFile(false, iter2->first, key);
WriteKeyToFile(category, iter2->first, key);
// Variable cases
if (id == S128KeyType::KeyArea) {
static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
"key_area_key_ocean_{:02X}",
"key_area_key_system_{:02X}"};
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
} else if (id == S128KeyType::Master) {
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
} else if (id == S128KeyType::Package1) {
WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
} else if (id == S128KeyType::Package2) {
WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
} else if (id == S128KeyType::Titlekek) {
WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
} else if (id == S128KeyType::Keyblob) {
WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
} else if (id == S128KeyType::KeyblobMAC) {
WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
} else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
}
s128_keys[{id, field1, field2}] = key;
}
@@ -260,7 +635,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter != s256_file_id.end())
WriteKeyToFile(false, iter->first, key);
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
s256_keys[{id, field1, field2}] = key;
}
@@ -290,59 +665,388 @@ void KeyManager::DeriveSDSeedLazy() {
SetKey(S128KeyType::SDSeed, res.get());
}
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
Key128 out{};
mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
key.size() * 8, source, size, out.data());
return out;
}
void KeyManager::DeriveBase() {
if (!BaseDeriveNecessary())
return;
if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
return;
const auto has_bis = [this](u64 id) {
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak));
};
const auto copy_bis = [this](u64 id_from, u64 id_to) {
SetKey(S128KeyType::BIS,
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to,
static_cast<u64>(BISKeyType::Crypto));
SetKey(S128KeyType::BIS,
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to,
static_cast<u64>(BISKeyType::Tweak));
};
if (has_bis(2) && !has_bis(3))
copy_bis(2, 3);
else if (has_bis(3) && !has_bis(2))
copy_bis(3, 2);
std::bitset<32> revisions(0xFFFFFFFF);
for (size_t i = 0; i < revisions.size(); ++i) {
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) ||
encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) {
revisions.reset(i);
}
}
if (!revisions.any())
return;
const auto sbk = GetKey(S128KeyType::SecureBoot);
const auto tsec = GetKey(S128KeyType::TSEC);
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
for (size_t i = 0; i < revisions.size(); ++i) {
if (!revisions[i])
continue;
// Derive keyblob key
const auto key = DeriveKeyblobKey(
sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i));
SetKey(S128KeyType::Keyblob, key, i);
// Derive keyblob MAC key
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
continue;
const auto mac_key = DeriveKeyblobMACKey(
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
continue;
// Decrypt keyblob
if (keyblobs[i] == std::array<u8, 0x90>{}) {
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
keyblobs[i]);
}
Key128 package1;
std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128));
SetKey(S128KeyType::Package1, package1, i);
// Derive master key
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) {
SetKey(S128KeyType::Master,
DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source,
static_cast<u64>(SourceKeyType::Master))),
i);
}
}
revisions.set();
for (size_t i = 0; i < revisions.size(); ++i) {
if (!HasKey(S128KeyType::Master, i))
revisions.reset(i);
}
if (!revisions.any())
return;
for (size_t i = 0; i < revisions.size(); ++i) {
if (!revisions[i])
continue;
// Derive general purpose keys
DeriveGeneralPurposeKeys(i);
}
if (HasKey(S128KeyType::Master, 0) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) &&
HasKey(S256KeyType::HeaderSource)) {
const auto header_kek = GenerateKeyEncryptionKey(
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)),
GetKey(S128KeyType::Master, 0),
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)),
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)));
SetKey(S128KeyType::HeaderKek, header_kek);
AESCipher<Key128> header_cipher(header_kek, Mode::ECB);
Key256 out = GetKey(S256KeyType::HeaderSource);
header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt);
SetKey(S256KeyType::Header, out);
}
}
void KeyManager::DeriveETicket(PartitionDataManager& data) {
// ETicket keys
const auto es = Service::FileSystem::GetUnionContents()->GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
if (es == nullptr)
return;
const auto exefs = es->GetExeFS();
if (exefs == nullptr)
return;
const auto main = exefs->GetFile("main");
if (main == nullptr)
return;
const auto bytes = main->ReadAllBytes();
const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]);
const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]);
const auto seed3 = data.GetRSAKekSeed3();
const auto mask0 = data.GetRSAKekMask0();
if (eticket_kek != Key128{})
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
if (eticket_kekek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kekek,
static_cast<size_t>(SourceKeyType::ETicketKekek));
}
if (seed3 != Key128{})
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
if (mask0 != Key128{})
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
mask0 == Key128{}) {
return;
}
Key128 rsa_oaep_kek{};
std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(),
std::bit_xor<>());
if (rsa_oaep_kek == Key128{})
return;
SetKey(S128KeyType::Source, rsa_oaep_kek,
static_cast<u64>(SourceKeyType::RSAOaepKekGeneration));
Key128 temp_kek{};
Key128 temp_kekek{};
Key128 eticket_final{};
// Derive ETicket RSA Kek
AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB);
es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt);
AESCipher<Key128> es_kekek(temp_kek, Mode::ECB);
es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt);
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
if (eticket_final == Key128{})
return;
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
// Titlekeys
data.DecryptProdInfo(GetBISKey(0));
const auto eticket_extended_kek = data.GetETicketExtendedKek();
std::vector<u8> extended_iv(0x10);
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
std::array<u8, 0x230> extended_dec{};
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
rsa_1.SetIV(extended_iv);
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
RSAKeyPair<2048> rsa_key{};
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e1",
"rb+");
const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e2",
"rb+");
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
res.insert(res.end(), blob2.begin(), blob2.end());
for (const auto& raw : res) {
const auto pair = ParseTicket(raw, rsa_key);
if (pair == boost::none)
continue;
const auto& [rid, key] = pair.value();
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
if (key == Key128{})
return;
SetKey(id, key, field1, field2);
}
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
if (key == Key256{})
return;
SetKey(id, key, field1, field2);
}
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
if (!BaseDeriveNecessary())
return;
if (!data.HasBoot0())
return;
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
continue;
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
encrypted_keyblobs[i]);
}
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
static_cast<u64>(SourceKeyType::Package2));
SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(),
static_cast<u64>(SourceKeyType::AESKekGeneration));
SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(),
static_cast<u64>(SourceKeyType::Titlekek));
SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(),
static_cast<u64>(SourceKeyType::Master));
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(),
static_cast<u64>(SourceKeyType::KeyblobMAC));
for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) {
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i),
static_cast<u64>(SourceKeyType::Keyblob), i);
}
if (data.HasFuses())
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
DeriveBase();
Key128 latest_master{};
for (s8 i = 0x1F; i >= 0; --i) {
if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) {
latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i));
break;
}
}
const auto masters = data.GetTZMasterKeys(latest_master);
for (size_t i = 0; i < masters.size(); ++i) {
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
SetKey(S128KeyType::Master, masters[i], i);
}
DeriveBase();
if (!data.HasPackage2())
return;
std::array<Key128, 0x20> package2_keys{};
for (size_t i = 0; i < package2_keys.size(); ++i) {
if (HasKey(S128KeyType::Package2, i))
package2_keys[i] = GetKey(S128KeyType::Package2, i);
}
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Application));
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Ocean));
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::System));
SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(),
static_cast<u64>(SourceKeyType::SDKek));
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(),
static_cast<u64>(SDKeyType::Save));
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(),
static_cast<u64>(SDKeyType::NCA));
SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(),
static_cast<u64>(SourceKeyType::HeaderKek));
SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource());
SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(),
static_cast<u64>(SourceKeyType::AESKeyGeneration));
DeriveBase();
}
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
{"master_key_00", {S128KeyType::Master, 0, 0}},
{"master_key_01", {S128KeyType::Master, 1, 0}},
{"master_key_02", {S128KeyType::Master, 2, 0}},
{"master_key_03", {S128KeyType::Master, 3, 0}},
{"master_key_04", {S128KeyType::Master, 4, 0}},
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
{"key_area_key_application_00",
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_01",
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_02",
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_03",
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_04",
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
{"eticket_rsa_kek_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
{"eticket_rsa_kekek_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
{"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
{"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
{"rsa_oaep_kek_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
{"aes_kek_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
{"aes_key_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
{"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
{"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
{"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
{"key_area_key_application_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_ocean_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_system_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::System)}},
{"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
{"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
{"tsec_key", {S128KeyType::TSEC, 0, 0}},
{"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
{"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
{"header_kek", {S128KeyType::HeaderKek, 0, 0}},
{"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
};
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
{"header_key", {S256KeyType::Header, 0, 0}},
{"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
{"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
{"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
};
} // namespace Core::Crypto

View File

@@ -5,11 +5,18 @@
#pragma once
#include <array>
#include <map>
#include <string>
#include <boost/container/flat_map.hpp>
#include <boost/optional.hpp>
#include <fmt/format.h>
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
namespace FileUtil {
class IOFile;
}
namespace Loader {
enum class ResultStatus : u16;
@@ -22,13 +29,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
using TicketRaw = std::array<u8, 0x400>;
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
template <size_t bit_size, size_t byte_size = (bit_size >> 3)>
struct RSAKeyPair {
std::array<u8, byte_size> encryption_key;
std::array<u8, byte_size> decryption_key;
std::array<u8, byte_size> modulus;
std::array<u8, 4> exponent;
};
enum class KeyCategory : u8 {
Standard,
Title,
Console,
};
enum class S256KeyType : u64 {
Header, //
SDKeySource, // f1=SDKeyType
SDKey, // f1=SDKeyType
Header, //
SDKeySource, // f1=SDKeyType
HeaderSource, //
};
enum class S128KeyType : u64 {
@@ -41,6 +65,14 @@ enum class S128KeyType : u64 {
SDSeed, //
Titlekey, // f1=rights id LSB f2=rights id MSB
Source, // f1=source type, f2= sub id
Keyblob, // f1=crypto revision
KeyblobMAC, // f1=crypto revision
TSEC, //
SecureBoot, //
BIS, // f1=partition (0-3), f2=type {crypt, tweak}
HeaderKek, //
SDKek, //
RSAKek, //
};
enum class KeyAreaKeyType : u8 {
@@ -50,9 +82,19 @@ enum class KeyAreaKeyType : u8 {
};
enum class SourceKeyType : u8 {
SDKEK,
AESKEKGeneration,
AESKeyGeneration,
SDKek, //
AESKekGeneration, //
AESKeyGeneration, //
RSAOaepKekGeneration, //
Master, //
Keyblob, // f2=crypto revision
KeyAreaKey, // f2=KeyAreaKeyType
Titlekek, //
Package2, //
HeaderKek, //
KeyblobMAC, //
ETicketKek, //
ETicketKekek, //
};
enum class SDKeyType : u8 {
@@ -60,6 +102,16 @@ enum class SDKeyType : u8 {
NCA,
};
enum class BISKeyType : u8 {
Crypto,
Tweak,
};
enum class RSAKekType : u8 {
Mask0,
Seed3,
};
template <typename KeyType>
struct KeyIndex {
KeyType type;
@@ -91,6 +143,8 @@ public:
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
Key256 GetBISKey(u8 partition_id) const;
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
@@ -100,23 +154,51 @@ public:
// 8*43 and the private file to exist.
void DeriveSDSeedLazy();
bool BaseDeriveNecessary() const;
void DeriveBase();
void DeriveETicket(PartitionDataManager& data);
void PopulateFromPartitionData(PartitionDataManager& data);
private:
boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title);
template <std::size_t Size>
void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
template <size_t Size>
void WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key);
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source);
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source);
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source);
std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
const Key128& key);
boost::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
// 0x140-0x144 is zero)
boost::optional<std::pair<Key128, Key128>> ParseTicket(
const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
} // namespace Core::Crypto

View File

@@ -0,0 +1,593 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// NOTE TO FUTURE MAINTAINERS:
// When a new version of switch cryptography is released,
// hash the new keyblob source and master key and add the hashes to
// the arrays below.
#include <algorithm>
#include <array>
#include <cctype>
#include <cstring>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/crypto/xts_encryption_layer.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
using namespace Common;
namespace Core::Crypto {
struct Package2Header {
std::array<u8, 0x100> signature;
Key128 header_ctr;
std::array<Key128, 4> section_ctr;
u32_le magic;
u32_le base_offset;
INSERT_PADDING_BYTES(4);
u8 version_max;
u8 version_min;
INSERT_PADDING_BYTES(2);
std::array<u32_le, 4> section_size;
std::array<u32_le, 4> section_offset;
std::array<SHA256Hash, 4> section_hash;
};
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
struct INIHeader {
u32_le magic;
u32_le size;
u32_le process_count;
INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
struct SectionHeader {
u32_le offset;
u32_le size_decompressed;
u32_le size_compressed;
u32_le attribute;
};
static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
struct KIPHeader {
u32_le magic;
std::array<char, 12> name;
u64_le title_id;
u32_le category;
u8 priority;
u8 core;
INSERT_PADDING_BYTES(1);
u8 flags;
std::array<SectionHeader, 6> sections;
std::array<u32, 0x20> capabilities;
};
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
const std::array<SHA256Hash, 0x10> source_hashes{
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
"21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
"FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
"C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
"04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
"FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
"1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
"6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
"D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
"2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
"1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
"8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
"D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
};
const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
"8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
"2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
"61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
"8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
"95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
"3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
};
const std::array<SHA256Hash, 0x20> master_key_hashes{
"0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
"4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
"79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
"4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
"75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
"EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
};
static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
const auto data_size = in.size() - 0xC;
u32 compressed_size{};
u32 init_index{};
u32 additional_size{};
std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
std::vector<u8> out(in.size() + additional_size);
if (compressed_size == in.size())
std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
else
std::memcpy(out.data(), in.data(), compressed_size);
auto index = in.size() - init_index;
auto out_index = out.size();
while (out_index > 0) {
--index;
auto control = in[index];
for (size_t i = 0; i < 8; ++i) {
if ((control & 0x80) > 0) {
ASSERT(index >= 2);
index -= 2;
u64 segment_offset = in[index] | in[index + 1] << 8;
u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
segment_offset &= 0xFFF;
segment_offset += 3;
if (out_index < segment_size)
segment_size = out_index;
ASSERT(out_index >= segment_size);
out_index -= segment_size;
for (size_t j = 0; j < segment_size; ++j) {
ASSERT(out_index + j + segment_offset < out.size());
out[out_index + j] = out[out_index + j + segment_offset];
}
} else {
ASSERT(out_index >= 1);
--out_index;
--index;
out[out_index] = in[index];
}
control <<= 1;
if (out_index == 0)
return out;
}
}
return out;
}
static u8 CalculateMaxKeyblobSourceHash() {
for (s8 i = 0x1F; i >= 0; --i) {
if (keyblob_source_hashes[i] != SHA256Hash{})
return static_cast<u8>(i + 1);
}
return 0;
}
const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash();
template <size_t key_size = 0x10>
std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary,
const std::array<u8, 0x20>& hash) {
if (binary.size() < key_size)
return {};
std::array<u8, 0x20> temp{};
for (size_t i = 0; i < binary.size() - key_size; ++i) {
mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0);
if (temp != hash)
continue;
std::array<u8, key_size> out{};
std::memcpy(out.data(), binary.data() + i, key_size);
return out;
}
return {};
}
std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) {
return FindKeyFromHex<0x10>(binary, hash);
}
static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary,
const Key128& key) {
if (binary.size() < 0x10)
return {};
SHA256Hash temp{};
Key128 dec_temp{};
std::array<Key128, 0x20> out{};
AESCipher<Key128> cipher(key, Mode::ECB);
for (size_t i = 0; i < binary.size() - 0x10; ++i) {
cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt);
mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0);
for (size_t k = 0; k < out.size(); ++k) {
if (temp == master_key_hashes[k]) {
out[k] = dec_temp;
break;
}
}
}
return out;
}
FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
const std::string& name) {
auto upper = name;
std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); });
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
if (dir->GetFile(fname) != nullptr)
return dir->GetFile(fname);
}
return nullptr;
}
PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir)
: boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
fuses(FindFileInDirWithNames(sysdata_dir, "fuses")),
kfuses(FindFileInDirWithNames(sysdata_dir, "kfuses")),
package2({
FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"),
}),
prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")),
secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")),
package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")),
secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{}
: secure_monitor->ReadAllBytes()),
package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{}
: package1_decrypted->ReadAllBytes()) {
}
PartitionDataManager::~PartitionDataManager() = default;
bool PartitionDataManager::HasBoot0() const {
return boot0 != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
return boot0;
}
PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob(
std::size_t index) const {
if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS)
return GetEncryptedKeyblobs()[index];
return {};
}
PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const {
if (!HasBoot0())
return {};
EncryptedKeyBlobs out{};
for (size_t i = 0; i < out.size(); ++i)
boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200);
return out;
}
std::vector<u8> PartitionDataManager::GetSecureMonitor() const {
return secure_monitor_bytes;
}
std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]);
}
std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]);
}
std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]);
}
std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys(
std::array<u8, 0x10> master_key) const {
return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key);
}
std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]);
}
std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]);
}
std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const {
return package1_decrypted_bytes;
}
std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const {
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]);
}
std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const {
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]);
}
std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(std::size_t revision) const {
if (keyblob_source_hashes[revision] == SHA256Hash{}) {
LOG_WARNING(Crypto,
"No keyblob source hash for crypto revision {:02X}! Cannot derive keys...",
revision);
}
return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]);
}
bool PartitionDataManager::HasFuses() const {
return fuses != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const {
return fuses;
}
std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const {
if (!HasFuses())
return {};
Key128 out{};
fuses->Read(out.data(), out.size(), 0xA4);
return out;
}
bool PartitionDataManager::HasKFuses() const {
return kfuses != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const {
return kfuses;
}
bool PartitionDataManager::HasPackage2(Package2Type type) const {
return package2.at(static_cast<size_t>(type)) != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const {
return package2.at(static_cast<size_t>(type));
}
bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
Package2Header temp = header;
AESCipher<Key128> cipher(key, Mode::CTR);
cipher.SetIV(iv);
cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
Op::Decrypt);
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
header = temp;
return true;
}
return false;
}
void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& package2_keys,
Package2Type type) {
FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>(
package2[static_cast<size_t>(type)],
package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000);
Package2Header header{};
if (file->ReadObject(&header) != sizeof(Package2Header))
return;
std::size_t revision = 0xFF;
if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
for (std::size_t i = 0; i < package2_keys.size(); ++i) {
if (AttemptDecrypt(package2_keys[i], header)) {
revision = i;
}
}
}
if (header.magic != Common::MakeMagic('P', 'K', '2', '1'))
return;
const auto a = std::make_shared<FileSys::OffsetVfsFile>(
file, header.section_size[1], header.section_size[0] + sizeof(Package2Header));
auto c = a->ReadAllBytes();
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
INIHeader ini;
std::memcpy(&ini, c.data(), sizeof(INIHeader));
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
return;
u64 offset = sizeof(INIHeader);
for (size_t i = 0; i < ini.process_count; ++i) {
KIPHeader kip;
std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
return;
const auto name =
Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
if (name != "FS" && name != "spl") {
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
continue;
}
const u64 initial_offset = sizeof(KIPHeader) + offset;
const auto text_begin = c.cbegin() + initial_offset;
const auto text_end = text_begin + kip.sections[0].size_compressed;
const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
const auto rodata_end = text_end + kip.sections[1].size_compressed;
const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
const auto data_end = rodata_end + kip.sections[2].size_compressed;
const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
std::vector<u8> out;
out.reserve(text.size() + rodata.size() + data.size());
out.insert(out.end(), text.begin(), text.end());
out.insert(out.end(), rodata.begin(), rodata.end());
out.insert(out.end(), data.begin(), data.end());
offset += sizeof(KIPHeader) + out.size();
if (name == "FS")
package2_fs[static_cast<size_t>(type)] = std::move(out);
else if (name == "spl")
package2_spl[static_cast<size_t>(type)] = std::move(out);
}
}
const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const {
return package2_fs.at(static_cast<size_t>(type));
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]);
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]);
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]);
}
std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]);
}
std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]);
}
std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]);
}
std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]);
}
std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]);
}
const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const {
return package2_spl.at(static_cast<size_t>(type));
}
std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const {
return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]);
}
bool PartitionDataManager::HasProdInfo() const {
return prodinfo != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const {
return prodinfo;
}
void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
if (prodinfo == nullptr)
return;
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
}
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
std::array<u8, 0x240> out{};
if (prodinfo_decrypted != nullptr)
prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
return out;
}
} // namespace Core::Crypto

View File

@@ -0,0 +1,109 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
namespace Core::Crypto {
enum class Package2Type {
NormalMain,
NormalSub,
SafeModeMain,
SafeModeSub,
RepairMain,
RepairSub,
};
class PartitionDataManager {
public:
static const u8 MAX_KEYBLOB_SOURCE_HASH;
static constexpr std::size_t NUM_ENCRYPTED_KEYBLOBS = 32;
static constexpr std::size_t ENCRYPTED_KEYBLOB_SIZE = 0xB0;
using EncryptedKeyBlob = std::array<u8, ENCRYPTED_KEYBLOB_SIZE>;
using EncryptedKeyBlobs = std::array<EncryptedKeyBlob, NUM_ENCRYPTED_KEYBLOBS>;
explicit PartitionDataManager(const FileSys::VirtualDir& sysdata_dir);
~PartitionDataManager();
// BOOT0
bool HasBoot0() const;
FileSys::VirtualFile GetBoot0Raw() const;
EncryptedKeyBlob GetEncryptedKeyblob(std::size_t index) const;
EncryptedKeyBlobs GetEncryptedKeyblobs() const;
std::vector<u8> GetSecureMonitor() const;
std::array<u8, 0x10> GetPackage2KeySource() const;
std::array<u8, 0x10> GetAESKekGenerationSource() const;
std::array<u8, 0x10> GetTitlekekSource() const;
std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const;
std::array<u8, 0x10> GetRSAKekSeed3() const;
std::array<u8, 0x10> GetRSAKekMask0() const;
std::vector<u8> GetPackage1Decrypted() const;
std::array<u8, 0x10> GetMasterKeySource() const;
std::array<u8, 0x10> GetKeyblobMACKeySource() const;
std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const;
// Fuses
bool HasFuses() const;
FileSys::VirtualFile GetFusesRaw() const;
std::array<u8, 0x10> GetSecureBootKey() const;
// K-Fuses
bool HasKFuses() const;
FileSys::VirtualFile GetKFusesRaw() const;
// Package2
bool HasPackage2(Package2Type type = Package2Type::NormalMain) const;
FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const;
void DecryptPackage2(const std::array<std::array<u8, 16>, 0x20>& package2_keys,
Package2Type type);
const std::vector<u8>& GetPackage2FSDecompressed(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeyApplicationSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeyOceanSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeySystemSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const;
const std::vector<u8>& GetPackage2SPLDecompressed(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetAESKeyGenerationSource(
Package2Type type = Package2Type::NormalMain) const;
// PRODINFO
bool HasProdInfo() const;
FileSys::VirtualFile GetProdInfoRaw() const;
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
std::array<u8, 0x240> GetETicketExtendedKek() const;
private:
FileSys::VirtualFile boot0;
FileSys::VirtualFile fuses;
FileSys::VirtualFile kfuses;
std::array<FileSys::VirtualFile, 6> package2;
FileSys::VirtualFile prodinfo;
FileSys::VirtualFile secure_monitor;
FileSys::VirtualFile package1_decrypted;
// Processed
std::array<FileSys::VirtualFile, 6> package2_decrypted;
FileSys::VirtualFile prodinfo_decrypted;
std::vector<u8> secure_monitor_bytes;
std::vector<u8> package1_decrypted_bytes;
std::array<std::vector<u8>, 6> package2_fs;
std::array<std::vector<u8>, 6> package2_spl;
};
std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash);
} // namespace Core::Crypto

View File

@@ -10,19 +10,19 @@ namespace FileSys {
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
sysnand_cache(std::make_shared<RegisteredCache>(
sysnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_shared<RegisteredCache>(
usrnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
BISFactory::~BISFactory() = default;
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
return sysnand_cache;
RegisteredCache* BISFactory::GetSystemNANDContents() const {
return sysnand_cache.get();
}
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
return usrnand_cache;
RegisteredCache* BISFactory::GetUserNANDContents() const {
return usrnand_cache.get();
}
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {

View File

@@ -20,8 +20,8 @@ public:
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
~BISFactory();
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
RegisteredCache* GetSystemNANDContents() const;
RegisteredCache* GetUserNANDContents() const;
VirtualDir GetModificationLoadRoot(u64 title_id) const;
@@ -29,8 +29,8 @@ private:
VirtualDir nand_root;
VirtualDir load_root;
std::shared_ptr<RegisteredCache> sysnand_cache;
std::shared_ptr<RegisteredCache> usrnand_cache;
std::unique_ptr<RegisteredCache> sysnand_cache;
std::unique_ptr<RegisteredCache> usrnand_cache;
};
} // namespace FileSys

View File

@@ -133,7 +133,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
static_cast<u8>(type));
u128 out_128{};
memcpy(out_128.data(), out.data(), 16);
LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;

View File

@@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = {
};
std::string LanguageEntry::GetApplicationName() const {
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
application_name.size());
}
std::string LanguageEntry::GetDeveloperName() const {
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
developer_name.size());
}
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
@@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const {
return raw->title_id;
}
u64 NACP::GetDLCBaseTitleId() const {
return raw->dlc_base_title_id;
}
std::string NACP::GetVersionString() const {
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
raw->version_string.size());
}
} // namespace FileSys

View File

@@ -79,6 +79,7 @@ public:
std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
private:

View File

@@ -214,8 +214,14 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
static_cast<u8>(type));
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type))
.c_str();
if (type == ContentRecordType::Program)
LOG_INFO(Loader, log_string);
else
LOG_DEBUG(Loader, log_string);
if (romfs == nullptr)
return romfs;
@@ -346,7 +352,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
const auto& installed{Service::FileSystem::GetUnionContents()};
const auto installed{Service::FileSystem::GetUnionContents()};
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)

View File

@@ -308,14 +308,14 @@ 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 {
std::unique_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);
return std::make_unique<NCA>(raw);
}
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
@@ -516,7 +516,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}) != yuzu_meta.end();
}
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
: caches(std::move(caches)) {}
void RegisteredCacheUnion::Refresh() {
@@ -572,14 +572,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const
return GetEntryRaw(entry.title_id, entry.type);
}
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
std::unique_ptr<NCA> RegisteredCacheUnion::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);
return std::make_unique<NCA>(raw);
}
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}

View File

@@ -88,8 +88,8 @@ public:
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::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
std::unique_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.
@@ -142,7 +142,7 @@ private:
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
class RegisteredCacheUnion {
public:
explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
void Refresh();
@@ -157,8 +157,8 @@ public:
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::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
std::unique_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.
@@ -168,7 +168,7 @@ public:
boost::optional<u64> title_id = boost::none) const;
private:
std::vector<std::shared_ptr<RegisteredCache>> caches;
std::vector<RegisteredCache*> caches;
};
} // namespace FileSys

View File

@@ -10,10 +10,10 @@
namespace FileSys {
SDMCFactory::SDMCFactory(VirtualDir dir_)
: dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
: dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
[](const VirtualFile& file, const NcaID& id) {
return std::make_shared<NAX>(file, id)->GetDecrypted();
return NAX{file, id}.GetDecrypted();
})) {}
SDMCFactory::~SDMCFactory() = default;
@@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() {
return MakeResult<VirtualDir>(dir);
}
std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
return contents;
RegisteredCache* SDMCFactory::GetSDMCContents() const {
return contents.get();
}
} // namespace FileSys

View File

@@ -19,12 +19,12 @@ public:
~SDMCFactory();
ResultVal<VirtualDir> Open();
std::shared_ptr<RegisteredCache> GetSDMCContents() const;
RegisteredCache* GetSDMCContents() const;
private:
VirtualDir dir;
std::shared_ptr<RegisteredCache> contents;
std::unique_ptr<RegisteredCache> contents;
};
} // namespace FileSys

View File

@@ -12,20 +12,12 @@
#include <vector>
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
namespace FileSys {
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>;
using VirtualFile = std::shared_ptr<VfsFile>;
// An enumeration representing what can be at the end of a path in a VfsFilesystem
enum class VfsEntryType {
None,

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
namespace FileSys {
class VfsDirectory;
class VfsFile;
class VfsFilesystem;
// Declarations for Vfs* pointer types
using VirtualDir = std::shared_ptr<VfsDirectory>;
using VirtualFile = std::shared_ptr<VfsFile>;
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
} // namespace FileSys

View File

@@ -207,7 +207,7 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
static Kernel::Thread* FindThreadById(int id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadID() == static_cast<u32>(id)) {
current_core = core;
@@ -597,7 +597,7 @@ static void HandleQuery() {
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
std::string val = "m";
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x}", thread->GetThreadID());
val += ",";
@@ -612,7 +612,7 @@ static void HandleQuery() {
buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>";
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (const auto& thread : threads) {
buffer +=
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",

View File

@@ -39,7 +39,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr arb_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
const auto& thread_list = scheduler->GetThreadList();
const auto& thread_list = scheduler.GetThreadList();
for (const auto& thread : thread_list) {
if (thread->GetArbiterWaitAddress() == arb_addr)

View File

@@ -22,6 +22,7 @@ enum {
HandleTableFull = 105,
InvalidMemoryState = 106,
InvalidMemoryPermissions = 108,
InvalidMemoryRange = 110,
InvalidThreadPriority = 112,
InvalidProcessorId = 113,
InvalidHandle = 114,
@@ -56,6 +57,7 @@ constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidA
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
ErrCodes::InvalidMemoryPermissions);
constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange);
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize);

View File

@@ -25,7 +25,6 @@ bool Object::IsWaitable() const {
case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
case HandleType::CodeSet:
case HandleType::ClientPort:
case HandleType::ClientSession:
return false;

View File

@@ -26,7 +26,6 @@ enum class HandleType : u32 {
AddressArbiter,
Timer,
ResourceLimit,
CodeSet,
ClientPort,
ServerPort,
ClientSession,

View File

@@ -20,13 +20,7 @@
namespace Kernel {
SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) {
SharedPtr<CodeSet> codeset(new CodeSet(kernel));
codeset->name = std::move(name);
return codeset;
}
CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {}
CodeSet::CodeSet() = default;
CodeSet::~CodeSet() = default;
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
@@ -159,11 +153,11 @@ void Process::PrepareForTermination() {
}
};
auto& system = Core::System::GetInstance();
stop_threads(system.Scheduler(0)->GetThreadList());
stop_threads(system.Scheduler(1)->GetThreadList());
stop_threads(system.Scheduler(2)->GetThreadList());
stop_threads(system.Scheduler(3)->GetThreadList());
const auto& system = Core::System::GetInstance();
stop_threads(system.Scheduler(0).GetThreadList());
stop_threads(system.Scheduler(1).GetThreadList());
stop_threads(system.Scheduler(2).GetThreadList());
stop_threads(system.Scheduler(3).GetThreadList());
}
/**
@@ -224,20 +218,20 @@ void Process::FreeTLSSlot(VAddr tls_address) {
tls_slots[tls_page].reset(tls_slot);
}
void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
auto vma = vm_manager
.MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset,
segment.size, memory_state)
.Unwrap();
const auto vma = vm_manager
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
segment.offset, segment.size, memory_state)
.Unwrap();
vm_manager.Reprotect(vma, permissions);
};
// Map CodeSet segments
MapSegment(module_->CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
MapSegment(module_->RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
MapSegment(module_->DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
}
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {

View File

@@ -24,6 +24,7 @@ class ProgramMetadata;
namespace Kernel {
class KernelCore;
class ResourceLimit;
struct AddressMapping {
// Address and size must be page-aligned
@@ -57,30 +58,33 @@ union ProcessFlags {
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
};
enum class ProcessStatus { Created, Running, Exited };
/**
* Indicates the status of a Process instance.
*
* @note These match the values as used by kernel,
* so new entries should only be added if RE
* shows that a new value has been introduced.
*/
enum class ProcessStatus {
Created,
CreatedWithDebuggerAttached,
Running,
WaitingForDebuggerToAttach,
DebuggerAttached,
Exiting,
Exited,
DebugBreak,
};
class ResourceLimit;
struct CodeSet final : public Object {
struct CodeSet final {
struct Segment {
std::size_t offset = 0;
VAddr addr = 0;
u32 size = 0;
};
static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name);
std::string GetTypeName() const override {
return "CodeSet";
}
std::string GetName() const override {
return name;
}
static const HandleType HANDLE_TYPE = HandleType::CodeSet;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
explicit CodeSet();
~CodeSet();
Segment& CodeSegment() {
return segments[0];
@@ -109,14 +113,7 @@ struct CodeSet final : public Object {
std::shared_ptr<std::vector<u8>> memory;
std::array<Segment, 3> segments;
VAddr entrypoint;
/// Name of the process
std::string name;
private:
explicit CodeSet(KernelCore& kernel);
~CodeSet() override;
VAddr entrypoint = 0;
};
class Process final : public Object {
@@ -219,7 +216,7 @@ public:
*/
void PrepareForTermination();
void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr);
void LoadModule(CodeSet module_, VAddr base_addr);
///////////////////////////////////////////////////////////////////////////////////////////////
// Memory Management

View File

@@ -39,6 +39,73 @@ namespace {
constexpr bool Is4KBAligned(VAddr address) {
return (address & 0xFFF) == 0;
}
// Checks if address + size is greater than the given address
// This can return false if the size causes an overflow of a 64-bit type
// or if the given size is zero.
constexpr bool IsValidAddressRange(VAddr address, u64 size) {
return address + size > address;
}
// Checks if a given address range lies within a larger address range.
constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
VAddr address_range_end) {
const VAddr end_address = address + size - 1;
return address_range_begin <= address && end_address <= address_range_end - 1;
}
bool IsInsideAddressSpace(const VMManager& vm, VAddr address, u64 size) {
return IsInsideAddressRange(address, size, vm.GetAddressSpaceBaseAddress(),
vm.GetAddressSpaceEndAddress());
}
bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) {
return IsInsideAddressRange(address, size, vm.GetNewMapRegionBaseAddress(),
vm.GetNewMapRegionEndAddress());
}
// Helper function that performs the common sanity checks for svcMapMemory
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
// in the same order.
ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
u64 size) {
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
return ERR_INVALID_ADDRESS;
}
if (size == 0 || !Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
if (!IsValidAddressRange(dst_addr, size)) {
return ERR_INVALID_ADDRESS_STATE;
}
if (!IsValidAddressRange(src_addr, size)) {
return ERR_INVALID_ADDRESS_STATE;
}
if (!IsInsideAddressSpace(vm_manager, src_addr, size)) {
return ERR_INVALID_ADDRESS_STATE;
}
if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) {
return ERR_INVALID_MEMORY_RANGE;
}
const VAddr dst_end_address = dst_addr + size;
if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() &&
vm_manager.GetHeapRegionEndAddress() > dst_addr) {
return ERR_INVALID_MEMORY_RANGE;
}
if (dst_end_address > vm_manager.GetMapRegionBaseAddress() &&
vm_manager.GetMapRegionEndAddress() > dst_addr) {
return ERR_INVALID_MEMORY_RANGE;
}
return RESULT_SUCCESS;
}
} // Anonymous namespace
/// Set the process heap to a given Size. It can both extend and shrink the heap.
@@ -69,15 +136,15 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
return ERR_INVALID_ADDRESS;
auto* const current_process = Core::CurrentProcess();
const auto& vm_manager = current_process->VMManager();
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
if (result != RESULT_SUCCESS) {
return result;
}
if (size == 0 || !Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
return current_process->MirrorMemory(dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -85,15 +152,15 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
return ERR_INVALID_ADDRESS;
auto* const current_process = Core::CurrentProcess();
const auto& vm_manager = current_process->VMManager();
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
if (result != RESULT_SUCCESS) {
return result;
}
if (size == 0 || !Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
return current_process->UnmapMemory(dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -303,15 +370,15 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
struct BreakReason {
union {
u64 raw;
BitField<31, 1, u64> dont_kill_application;
u32 raw;
BitField<31, 1, u32> signal_debugger;
};
};
/// Break program execution
static void Break(u64 reason, u64 info1, u64 info2) {
static void Break(u32 reason, u64 info1, u64 info2) {
BreakReason break_reason{reason};
if (break_reason.dont_kill_application) {
if (break_reason.signal_debugger) {
LOG_ERROR(
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@@ -322,6 +389,12 @@ static void Break(u64 reason, u64 info1, u64 info2) {
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
reason, info1, info2);
ASSERT(false);
Core::CurrentProcess()->PrepareForTermination();
// Kill the current thread
GetCurrentThread()->Stop();
Core::System::GetInstance().PrepareReschedule();
}
}
@@ -736,7 +809,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr condvar_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
const auto& thread_list = scheduler->GetThreadList();
const auto& thread_list = scheduler.GetThreadList();
for (const auto& thread : thread_list) {
if (thread->GetCondVarWaitAddress() == condvar_addr)
@@ -1025,6 +1098,29 @@ static ResultCode ClearEvent(Handle handle) {
return RESULT_SUCCESS;
}
static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
// This function currently only allows retrieving a process' status.
enum class InfoType {
Status,
};
const auto& kernel = Core::System::GetInstance().Kernel();
const auto process = kernel.HandleTable().Get<Process>(process_handle);
if (!process) {
return ERR_INVALID_HANDLE;
}
const auto info_type = static_cast<InfoType>(type);
if (info_type != InfoType::Status) {
return ERR_INVALID_ENUM_VALUE;
}
*out = static_cast<u64>(process->GetStatus());
return RESULT_SUCCESS;
}
namespace {
struct FunctionDef {
using Func = void();
@@ -1160,7 +1256,7 @@ static const FunctionDef SVC_Table[] = {
{0x79, nullptr, "CreateProcess"},
{0x7A, nullptr, "StartProcess"},
{0x7B, nullptr, "TerminateProcess"},
{0x7C, nullptr, "GetProcessInfo"},
{0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"},
{0x7D, nullptr, "CreateResourceLimit"},
{0x7E, nullptr, "SetResourceLimitLimitValue"},
{0x7F, nullptr, "CallSecureMonitor"},

View File

@@ -35,18 +35,18 @@ void SvcWrap() {
template <ResultCode func(u32)>
void SvcWrap() {
FuncReturn(func((u32)Param(0)).raw);
FuncReturn(func(static_cast<u32>(Param(0))).raw);
}
template <ResultCode func(u32, u32)>
void SvcWrap() {
FuncReturn(func((u32)Param(0), (u32)Param(1)).raw);
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw);
}
template <ResultCode func(u32*, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, (u32)Param(1)).raw;
u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -61,7 +61,7 @@ void SvcWrap() {
template <ResultCode func(u64, s32)>
void SvcWrap() {
FuncReturn(func(Param(0), (s32)Param(1)).raw);
FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw);
}
template <ResultCode func(u64, u32)>
@@ -77,21 +77,29 @@ void SvcWrap() {
FuncReturn(retval);
}
template <ResultCode func(u64*, u32, u32)>
void SvcWrap() {
u64 param_1 = 0;
u32 retval = func(&param_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
template <ResultCode func(u32, u64)>
void SvcWrap() {
FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), Param(1)).raw);
FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw);
}
template <ResultCode func(u32, u32, u64)>
void SvcWrap() {
FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), (u32)(Param(1) & 0xFFFFFFFF), Param(2)).raw);
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw);
}
template <ResultCode func(u32, u32*, u64*)>
void SvcWrap() {
u32 param_1 = 0;
u64 param_2 = 0;
ResultCode retval = func((u32)(Param(2) & 0xFFFFFFFF), &param_1, &param_2);
ResultCode retval = func(static_cast<u32>(Param(2)), &param_1, &param_2);
Core::CurrentArmInterface().SetReg(1, param_1);
Core::CurrentArmInterface().SetReg(2, param_2);
FuncReturn(retval.raw);
@@ -100,12 +108,12 @@ void SvcWrap() {
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
func(Param(0), Param(1), (u32)(Param(3) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw);
func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u32, u64, u32)>
void SvcWrap() {
FuncReturn(func((u32)Param(0), Param(1), (u32)Param(2)).raw);
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw);
}
template <ResultCode func(u64, u64, u64)>
@@ -115,25 +123,28 @@ void SvcWrap() {
template <ResultCode func(u32, u64, u64, u32)>
void SvcWrap() {
FuncReturn(func((u32)Param(0), Param(1), Param(2), (u32)Param(3)).raw);
FuncReturn(
func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u32, u64, u64)>
void SvcWrap() {
FuncReturn(func((u32)Param(0), Param(1), Param(2)).raw);
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw);
}
template <ResultCode func(u32*, u64, u64, s64)>
void SvcWrap() {
u32 param_1 = 0;
ResultCode retval = func(&param_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (s64)Param(3));
ResultCode retval =
func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3)));
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval.raw);
}
template <ResultCode func(u64, u64, u32, s64)>
void SvcWrap() {
FuncReturn(func(Param(0), Param(1), (u32)Param(2), (s64)Param(3)).raw);
FuncReturn(
func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw);
}
template <ResultCode func(u64*, u64, u64, u64)>
@@ -147,9 +158,9 @@ void SvcWrap() {
template <ResultCode func(u32*, u64, u64, u64, u32, s32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval =
func(&param_1, Param(1), Param(2), Param(3), (u32)Param(4), (s32)(Param(5) & 0xFFFFFFFF))
.raw;
u32 retval = func(&param_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)),
static_cast<s32>(Param(5)))
.raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -172,7 +183,7 @@ void SvcWrap() {
template <ResultCode func(u32*, u64, u64, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, Param(1), Param(2), (u32)(Param(3) & 0xFFFFFFFF)).raw;
u32 retval = func(&param_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -181,22 +192,22 @@ template <ResultCode func(Handle*, u64, u32, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval =
func(&param_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw;
func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
template <ResultCode func(u64, u32, s32, s64)>
void SvcWrap() {
FuncReturn(
func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), (s64)Param(3))
.raw);
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
static_cast<s64>(Param(3)))
.raw);
}
template <ResultCode func(u64, u32, s32, s32)>
void SvcWrap() {
FuncReturn(func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF),
(s32)(Param(3) & 0xFFFFFFFF))
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
static_cast<s32>(Param(3)))
.raw);
}
@@ -226,7 +237,7 @@ void SvcWrap() {
template <void func(s64)>
void SvcWrap() {
func((s64)Param(0));
func(static_cast<s64>(Param(0)));
}
template <void func(u64, u64 len)>
@@ -239,4 +250,9 @@ void SvcWrap() {
func(Param(0), Param(1), Param(2));
}
template <void func(u32, u64, u64)>
void SvcWrap() {
func(static_cast<u32>(Param(0)), Param(1), Param(2));
}
} // namespace Kernel

View File

@@ -97,7 +97,7 @@ void Thread::CancelWakeupTimer() {
static boost::optional<s32> GetNextProcessorId(u64 mask) {
for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
if (mask & (1ULL << index)) {
if (!Core::System::GetInstance().Scheduler(index)->GetCurrentThread()) {
if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) {
// Core is enabled and not running any threads, use this one
return index;
}
@@ -147,14 +147,14 @@ void Thread::ResumeFromWait() {
new_processor_id = processor_id;
}
if (ideal_core != -1 &&
Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
new_processor_id = ideal_core;
}
ASSERT(*new_processor_id < 4);
// Add thread to new core's scheduler
auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
if (*new_processor_id != processor_id) {
// Remove thread from previous core's scheduler
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
scheduler = next_scheduler.get();
scheduler = next_scheduler;
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
@@ -183,13 +183,10 @@ void Thread::ResumeFromWait() {
*/
static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAddr stack_top,
VAddr entry_point, u64 arg) {
memset(&context, 0, sizeof(Core::ARM_Interface::ThreadContext));
context = {};
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
context.pstate = 0;
context.fpcr = 0;
}
ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point,
@@ -233,7 +230,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
thread->name = std::move(name);
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
thread->owner_process = &owner_process;
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
thread->scheduler = &Core::System::GetInstance().Scheduler(processor_id);
thread->scheduler->AddThread(thread, priority);
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
@@ -378,14 +375,14 @@ void Thread::ChangeCore(u32 core, u64 mask) {
new_processor_id = processor_id;
}
if (ideal_core != -1 &&
Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
new_processor_id = ideal_core;
}
ASSERT(*new_processor_id < 4);
// Add thread to new core's scheduler
auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
if (*new_processor_id != processor_id) {
// Remove thread from previous core's scheduler
@@ -400,7 +397,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
scheduler = next_scheduler.get();
scheduler = next_scheduler;
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}

View File

@@ -7,8 +7,10 @@
#include <vector>
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
@@ -19,7 +21,7 @@
namespace Service::AOC {
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
constexpr u64 DLC_BASE_TO_AOC_ID_MASK = 0x1000;
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
@@ -97,14 +99,24 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
ctx.WriteBuffer(out);
IPC::ResponseBuilder rb{ctx, 2};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(count);
}
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(Core::System::GetInstance().CurrentProcess()->GetTitleID() | DLC_BASE_TO_AOC_ID_MASK);
const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
FileSys::PatchManager pm{title_id};
const auto res = pm.GetControlMetadata();
if (res.first == nullptr) {
rb.Push(title_id + DLC_BASE_TO_AOC_ID);
return;
}
rb.Push(res.first->GetDLCBaseTitleId());
}
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {

View File

@@ -2,8 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <cstring>
#include <memory>
#include <optional>
#include <vector>
#include <opus.h>
@@ -33,7 +35,8 @@ public:
{1, nullptr, "SetContext"},
{2, nullptr, "DecodeInterleavedForMultiStream"},
{3, nullptr, "SetContextForMultiStream"},
{4, nullptr, "Unknown4"},
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerformance,
"DecodeInterleavedWithPerformance"},
{5, nullptr, "Unknown5"},
{6, nullptr, "Unknown6"},
{7, nullptr, "Unknown7"},
@@ -59,8 +62,31 @@ private:
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
}
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
std::vector<opus_int16>& output) {
void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) {
u32 consumed = 0;
u32 sample_count = 0;
u64 performance = 0;
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
performance)) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(ResultCode(-1));
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(consumed);
rb.Push<u64>(performance);
rb.Push<u32>(sample_count);
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
}
bool Decoder_DecodeInterleaved(
u32& consumed, u32& sample_count, const std::vector<u8>& input,
std::vector<opus_int16>& output,
std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) {
const auto start_time = std::chrono::high_resolution_clock::now();
std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
if (sizeof(OpusHeader) > input.size())
return false;
@@ -80,8 +106,13 @@ private:
(static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0);
if (out_sample_count < 0)
return false;
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
sample_count = out_sample_count;
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
if (performance_time.has_value()) {
performance_time->get() =
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
}
return true;
}

View File

@@ -319,13 +319,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
return sdmc_factory->Open();
}
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
return std::make_shared<FileSys::RegisteredCacheUnion>(
std::vector<std::shared_ptr<FileSys::RegisteredCache>>{
GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
return std::make_unique<FileSys::RegisteredCacheUnion>(std::vector<FileSys::RegisteredCache*>{
GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
}
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
FileSys::RegisteredCache* GetSystemNANDContents() {
LOG_TRACE(Service_FS, "Opening System NAND Contents");
if (bis_factory == nullptr)
@@ -334,7 +333,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
return bis_factory->GetSystemNANDContents();
}
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
FileSys::RegisteredCache* GetUserNANDContents() {
LOG_TRACE(Service_FS, "Opening User NAND Contents");
if (bis_factory == nullptr)
@@ -343,7 +342,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
return bis_factory->GetUserNANDContents();
}
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
FileSys::RegisteredCache* GetSDMCContents() {
LOG_TRACE(Service_FS, "Opening SDMC Contents");
if (sdmc_factory == nullptr)
@@ -361,19 +360,19 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
return bis_factory->GetModificationLoadRoot(title_id);
}
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (overwrite) {
bis_factory = nullptr;
save_data_factory = nullptr;
sdmc_factory = nullptr;
}
auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
FileSys::Mode::ReadWrite);
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
FileSys::Mode::ReadWrite);
auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
FileSys::Mode::ReadWrite);
auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
FileSys::Mode::ReadWrite);
if (bis_factory == nullptr)
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
@@ -383,7 +382,7 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
}
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) {
romfs_factory = nullptr;
CreateFactories(vfs, false);
std::make_shared<FSP_LDR>()->InstallAsService(service_manager);

View File

@@ -47,19 +47,19 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSDMC();
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
FileSys::RegisteredCache* GetSystemNANDContents();
FileSys::RegisteredCache* GetUserNANDContents();
FileSys::RegisteredCache* GetSDMCContents();
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs);
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and

View File

@@ -161,7 +161,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
};
RegisterHandlers(functions);
// Attempt to load shared font data from disk
const auto nand = FileSystem::GetSystemNANDContents();
const auto* nand = FileSystem::GetSystemNANDContents();
std::size_t offset = 0;
// Rebuild shared fonts from data ncas
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),

View File

@@ -15,6 +15,11 @@
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
namespace NvErrCodes {
enum {
InvalidNmapHandle = -22,
};
}
nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
@@ -79,14 +84,16 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
std::memcpy(entries.data(), input.data(), input.size());
auto& gpu = Core::System::GetInstance().GPU();
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
ASSERT(object);
if (!object) {
LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
std::memcpy(output.data(), entries.data(), output.size());
return static_cast<u32>(NvErrCodes::InvalidNmapHandle);
}
ASSERT(object->status == nvmap::Object::Status::Allocated);
@@ -167,10 +174,11 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
auto& system_instance = Core::System::GetInstance();
// Remove this memory region from the rasterizer cache.
system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(params.offset,
itr->second.size);
auto& gpu = system_instance.GPU();
auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
ASSERT(cpu_addr);
system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
buffer_mappings.erase(itr->second.offset);

View File

@@ -11,6 +11,13 @@
namespace Service::Nvidia::Devices {
namespace NvErrCodes {
enum {
OperationNotPermitted = -1,
InvalidValue = -22,
};
}
nvmap::nvmap() = default;
nvmap::~nvmap() = default;
@@ -44,7 +51,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o
u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
if (!params.size) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
// Create a new nvmap object and obtain a handle to it.
auto object = std::make_shared<Object>();
object->id = next_id++;
@@ -55,8 +66,6 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
u32 handle = next_handle++;
handles[handle] = std::move(object);
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
params.handle = handle;
std::memcpy(output.data(), &params, sizeof(params));
@@ -66,9 +75,29 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
if (!params.handle) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
if ((params.align - 1) & params.align) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
const u32 min_alignment = 0x1000;
if (params.align < min_alignment) {
params.align = min_alignment;
}
auto object = GetObject(params.handle);
ASSERT(object);
if (!object) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
if (object->status == Object::Status::Allocated) {
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
}
object->flags = params.flags;
object->align = params.align;
@@ -76,8 +105,6 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
object->addr = params.addr;
object->status = Object::Status::Allocated;
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
std::memcpy(output.data(), &params, sizeof(params));
return 0;
}
@@ -88,8 +115,14 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "called");
if (!params.handle) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
auto object = GetObject(params.handle);
ASSERT(object);
if (!object) {
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
}
params.id = object->id;
@@ -105,7 +138,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
auto itr = std::find_if(handles.begin(), handles.end(),
[&](const auto& entry) { return entry.second->id == params.id; });
ASSERT(itr != handles.end());
if (itr == handles.end()) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
auto& object = itr->second;
if (object->status != Object::Status::Allocated) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
itr->second->refcount++;
@@ -125,8 +165,13 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param);
auto object = GetObject(params.handle);
ASSERT(object);
ASSERT(object->status == Object::Status::Allocated);
if (!object) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
if (object->status != Object::Status::Allocated) {
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
}
switch (static_cast<ParamTypes>(params.param)) {
case ParamTypes::Size:
@@ -163,9 +208,12 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
auto itr = handles.find(params.handle);
ASSERT(itr != handles.end());
ASSERT(itr->second->refcount > 0);
if (itr == handles.end()) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
if (!itr->second->refcount) {
return static_cast<u32>(NvErrCodes::InvalidValue);
}
itr->second->refcount--;

View File

@@ -197,7 +197,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
// Module interface
/// Initialize ServiceManager
void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) {
void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
@@ -220,7 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
EUPLD::InstallInterfaces(*sm);
Fatal::InstallInterfaces(*sm);
FGM::InstallInterfaces(*sm);
FileSystem::InstallInterfaces(*sm, rfs);
FileSystem::InstallInterfaces(*sm, vfs);
Friend::InstallInterfaces(*sm);
GRC::InstallInterfaces(*sm);
HID::InstallInterfaces(*sm);

View File

@@ -180,8 +180,7 @@ private:
};
/// Initialize ServiceManager
void Init(std::shared_ptr<SM::ServiceManager>& sm,
const std::shared_ptr<FileSys::VfsFilesystem>& vfs);
void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs);
/// Shutdown ServiceManager
void Shutdown();

View File

@@ -968,6 +968,54 @@ private:
rb.PushCopyObjects(vsync_event);
}
enum class ConvertedScaleMode : u64 {
None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no
// scaling/default
Freeze = 1,
ScaleToWindow = 2,
Crop = 3,
NoCrop = 4,
};
// This struct is different, currently it's 1:1 but this might change in the future.
enum class NintendoScaleMode : u32 {
None = 0,
Freeze = 1,
ScaleToWindow = 2,
Crop = 3,
NoCrop = 4,
};
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto mode = rp.PopEnum<NintendoScaleMode>();
LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
switch (mode) {
case NintendoScaleMode::None:
rb.PushEnum(ConvertedScaleMode::None);
break;
case NintendoScaleMode::Freeze:
rb.PushEnum(ConvertedScaleMode::Freeze);
break;
case NintendoScaleMode::ScaleToWindow:
rb.PushEnum(ConvertedScaleMode::ScaleToWindow);
break;
case NintendoScaleMode::Crop:
rb.PushEnum(ConvertedScaleMode::Crop);
break;
case NintendoScaleMode::NoCrop:
rb.PushEnum(ConvertedScaleMode::NoCrop);
break;
default:
UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode));
rb.PushEnum(ConvertedScaleMode::None);
break;
}
}
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
@@ -991,7 +1039,7 @@ IApplicationDisplayService::IApplicationDisplayService(
{2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
{2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
{2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
{2102, nullptr, "ConvertScalingMode"},
{2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
{2450, nullptr, "GetIndirectLayerImageMap"},
{2451, nullptr, "GetIndirectLayerImageCropMap"},
{2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},

View File

@@ -139,14 +139,22 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
const FileSys::VirtualFile module_file = dir->GetFile(module);
if (module_file != nullptr) {
const VAddr load_addr = next_load_addr;
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr,
std::strcmp(module, "rtld") == 0, pm);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
if (module_file == nullptr) {
continue;
}
const VAddr load_addr = next_load_addr;
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr =
AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm);
if (!tentative_next_load_addr) {
return ResultStatus::ErrorLoadingNSO;
}
next_load_addr = *tentative_next_load_addr;
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
}
process.Run(base_address, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize());

View File

@@ -9,16 +9,11 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/elf.h"
#include "core/memory.h"
using Kernel::CodeSet;
using Kernel::SharedPtr;
////////////////////////////////////////////////////////////////////////////////////////////////////
// ELF Header Constants
@@ -211,7 +206,7 @@ public:
u32 GetFlags() const {
return (u32)(header->e_flags);
}
SharedPtr<CodeSet> LoadInto(VAddr vaddr);
Kernel::CodeSet LoadInto(VAddr vaddr);
int GetNumSegments() const {
return (int)(header->e_phnum);
@@ -274,7 +269,7 @@ const char* ElfReader::GetSectionName(int section) const {
return nullptr;
}
SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
// Should we relocate?
@@ -302,8 +297,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
std::vector<u8> program_image(total_image_size);
std::size_t current_image_position = 0;
auto& kernel = Core::System::GetInstance().Kernel();
SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, "");
Kernel::CodeSet codeset;
for (unsigned int i = 0; i < header->e_phnum; ++i) {
const Elf32_Phdr* p = &segments[i];
@@ -311,14 +305,14 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
p->p_vaddr, p->p_filesz, p->p_memsz);
if (p->p_type == PT_LOAD) {
CodeSet::Segment* codeset_segment;
Kernel::CodeSet::Segment* codeset_segment;
u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X);
if (permission_flags == (PF_R | PF_X)) {
codeset_segment = &codeset->CodeSegment();
codeset_segment = &codeset.CodeSegment();
} else if (permission_flags == (PF_R)) {
codeset_segment = &codeset->RODataSegment();
codeset_segment = &codeset.RODataSegment();
} else if (permission_flags == (PF_R | PF_W)) {
codeset_segment = &codeset->DataSegment();
codeset_segment = &codeset.DataSegment();
} else {
LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
p->p_flags);
@@ -345,8 +339,8 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
}
}
codeset->entrypoint = base_addr + header->e_entry;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
codeset.entrypoint = base_addr + header->e_entry;
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
LOG_DEBUG(Loader, "Done loading.");
@@ -397,11 +391,11 @@ ResultStatus AppLoader_ELF::Load(Kernel::Process& process) {
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
ElfReader elf_reader(&buffer[0]);
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(base_address);
codeset->name = file->GetName();
Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
const VAddr entry_point = codeset.entrypoint;
process.LoadModule(codeset, codeset->entrypoint);
process.Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
process.LoadModule(std::move(codeset), entry_point);
process.Run(entry_point, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;

View File

@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
constexpr std::array<const char*, 59> RESULT_MESSAGES{
constexpr std::array<const char*, 60> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -128,6 +128,7 @@ constexpr std::array<const char*, 59> RESULT_MESSAGES{
"The RomFS could not be found.",
"The ELF file has incorrect size as determined by the header.",
"There was a general error loading the NRO into emulated memory.",
"There was a general error loading the NSO into emulated memory.",
"There is no icon available.",
"There is no control data available.",
"The NAX file has a bad header.",

View File

@@ -90,6 +90,7 @@ enum class ResultStatus : u16 {
ErrorNoRomFS,
ErrorIncorrectELFFileSize,
ErrorLoadingNRO,
ErrorLoadingNSO,
ErrorNoIcon,
ErrorNoControl,
ErrorBadNAXHeader,

View File

@@ -14,7 +14,6 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs_offset.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nro.h"
@@ -128,10 +127,10 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
if (sizeof(NroHeader) != file.ReadObject(&nro_header)) {
return {};
}
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
@@ -139,22 +138,21 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
}
// Build program image
auto& kernel = Core::System::GetInstance().Kernel();
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
std::vector<u8> program_image = file.ReadBytes(PageAlignSize(nro_header.file_size));
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
}
Kernel::CodeSet codeset;
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
codeset->segments[i].addr = nro_header.segments[i].offset;
codeset->segments[i].offset = nro_header.segments[i].offset;
codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size);
codeset.segments[i].addr = nro_header.segments[i].offset;
codeset.segments[i].offset = nro_header.segments[i].offset;
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
}
if (!Settings::values.program_args.empty()) {
const auto arg_data = Settings::values.program_args;
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
@@ -176,16 +174,15 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
codeset->DataSegment().size += bss_size;
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Load codeset for current process
codeset->name = file->GetName();
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(codeset, load_base);
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(codeset->name, load_base, load_base);
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
return true;
}
@@ -198,7 +195,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
// Load NRO
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadNro(file, base_address)) {
if (!LoadNro(*file, base_address)) {
return ResultStatus::ErrorLoadingNRO;
}

View File

@@ -41,7 +41,7 @@ public:
bool IsRomFSUpdatable() const override;
private:
bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;

View File

@@ -12,7 +12,6 @@
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
@@ -94,42 +93,38 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
bool should_pass_arguments,
boost::optional<FileSys::PatchManager> pm) {
if (file == nullptr)
return {};
if (file->GetSize() < sizeof(NsoHeader))
std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm) {
if (file.GetSize() < sizeof(NsoHeader))
return {};
NsoHeader nso_header{};
if (sizeof(NsoHeader) != file->ReadObject(&nso_header))
if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
return {};
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return {};
// Build program image
auto& kernel = Core::System::GetInstance().Kernel();
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
Kernel::CodeSet codeset;
std::vector<u8> program_image;
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
program_image.resize(nso_header.segments[i].location);
program_image.insert(program_image.end(), data.begin(), data.end());
codeset->segments[i].addr = nso_header.segments[i].location;
codeset->segments[i].offset = nso_header.segments[i].location;
codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
codeset.segments[i].addr = nso_header.segments[i].location;
codeset.segments[i].offset = nso_header.segments[i].location;
codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
}
if (should_pass_arguments && !Settings::values.program_args.empty()) {
const auto arg_data = Settings::values.program_args;
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
@@ -154,12 +149,12 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
codeset->DataSegment().size += bss_size;
codeset.DataSegment().size += bss_size;
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
program_image.resize(image_size);
// Apply patches if necessary
if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
if (pm && pm->HasNSOPatch(nso_header.build_id)) {
std::vector<u8> pi_header(program_image.size() + 0x100);
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
@@ -170,12 +165,11 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
}
// Load codeset for current process
codeset->name = file->GetName();
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(codeset, load_base);
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(codeset->name, load_base, load_base);
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
return load_base + image_size;
}
@@ -187,7 +181,9 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
LoadModule(file, base_address, true);
if (!LoadModule(*file, base_address, true)) {
return ResultStatus::ErrorLoadingNSO;
}
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);

View File

@@ -4,6 +4,7 @@
#pragma once
#include <optional>
#include "common/common_types.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/linker.h"
@@ -36,8 +37,9 @@ public:
return IdentifyType(file);
}
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments,
boost::optional<FileSys::PatchManager> pm = boost::none);
static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm = {});
ResultStatus Load(Kernel::Process& process) override;
};

View File

@@ -136,7 +136,7 @@ struct Values {
float resolution_factor;
bool use_frame_limit;
u16 frame_limit;
bool use_accurate_framebuffers;
bool use_accurate_gpu_emulation;
float bg_red;
float bg_green;

View File

@@ -163,8 +163,8 @@ TelemetrySession::TelemetrySession() {
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, "Renderer_UseAccurateGpuEmulation",
Settings::values.use_accurate_gpu_emulation);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
Settings::values.use_docked_mode);
}

View File

@@ -62,14 +62,16 @@ void Fermi2D::HandleSurfaceCopy() {
u8* dst_buffer = Memory::GetPointer(dest_cpu);
if (!regs.src.linear && regs.dst.linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
regs.src.BlockHeight());
Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
src_bytes_per_pixel, dst_bytes_per_pixel, src_buffer,
dst_buffer, true, regs.src.BlockHeight(),
regs.src.BlockDepth());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
regs.dst.BlockHeight());
Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
src_bytes_per_pixel, dst_bytes_per_pixel, dst_buffer,
src_buffer, false, regs.dst.BlockHeight(),
regs.dst.BlockDepth());
}
}
}

View File

@@ -36,9 +36,9 @@ public:
RenderTargetFormat format;
BitField<0, 1, u32> linear;
union {
BitField<0, 4, u32> block_depth;
BitField<0, 4, u32> block_width;
BitField<4, 4, u32> block_height;
BitField<8, 4, u32> block_width;
BitField<8, 4, u32> block_depth;
};
u32 depth;
u32 layer;
@@ -53,10 +53,20 @@ public:
address_low);
}
u32 BlockWidth() const {
// The block width is stored in log2 format.
return 1 << block_width;
}
u32 BlockHeight() const {
// The block height is stored in log2 format.
return 1 << block_height;
}
u32 BlockDepth() const {
// The block depth is stored in log2 format.
return 1 << block_depth;
}
};
static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");

View File

@@ -347,6 +347,16 @@ public:
DecrWrap = 8,
};
enum class MemoryLayout : u32 {
Linear = 0,
BlockLinear = 1,
};
enum class InvMemoryLayout : u32 {
BlockLinear = 0,
Linear = 1,
};
struct Cull {
enum class FrontFace : u32 {
ClockWise = 0x0900,
@@ -432,8 +442,16 @@ public:
u32 width;
u32 height;
Tegra::RenderTargetFormat format;
u32 block_dimensions;
u32 array_mode;
union {
BitField<0, 3, u32> block_width;
BitField<4, 3, u32> block_height;
BitField<8, 3, u32> block_depth;
BitField<12, 1, InvMemoryLayout> type;
} memory_layout;
union {
BitField<0, 16, u32> array_mode;
BitField<16, 1, u32> volume;
};
u32 layer_stride;
u32 base_layer;
INSERT_PADDING_WORDS(7);
@@ -562,7 +580,12 @@ public:
u32 address_high;
u32 address_low;
Tegra::DepthFormat format;
u32 block_dimensions;
union {
BitField<0, 4, u32> block_width;
BitField<4, 4, u32> block_height;
BitField<8, 4, u32> block_depth;
BitField<20, 1, InvMemoryLayout> type;
} memory_layout;
u32 layer_stride;
GPUVAddr Address() const {

View File

@@ -68,12 +68,14 @@ void MaxwellDMA::HandleCopy() {
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer,
dst_buffer, true, regs.src_params.BlockHeight());
Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y,
regs.src_params.size_z, 1, 1, src_buffer, dst_buffer, true,
regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer,
src_buffer, false, regs.dst_params.BlockHeight());
Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y,
regs.dst_params.size_z, 1, 1, dst_buffer, src_buffer, false,
regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
}
}

View File

@@ -43,6 +43,10 @@ public:
u32 BlockHeight() const {
return 1 << block_height;
}
u32 BlockDepth() const {
return 1 << block_depth;
}
};
static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");

View File

@@ -214,6 +214,18 @@ enum class IMinMaxExchange : u64 {
XHi = 3,
};
enum class VmadType : u64 {
Size16_Low = 0,
Size16_High = 1,
Size32 = 2,
Invalid = 3,
};
enum class VmadShr : u64 {
Shr7 = 1,
Shr15 = 2,
};
enum class XmadMode : u64 {
None = 0,
CLo = 1,
@@ -255,7 +267,7 @@ enum class ControlCode : u64 {
GTU = 12,
NEU = 13,
GEU = 14,
//
T = 15,
OFF = 16,
LO = 17,
SFF = 18,
@@ -452,6 +464,7 @@ union Instruction {
BitField<48, 16, u64> opcode;
union {
BitField<20, 16, u64> imm20_16;
BitField<20, 19, u64> imm20_19;
BitField<20, 32, s64> imm20_32;
BitField<45, 1, u64> negate_b;
@@ -493,6 +506,10 @@ union Instruction {
}
} lop3;
u16 GetImm20_16() const {
return static_cast<u16>(imm20_16);
}
u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
@@ -1016,6 +1033,23 @@ union Instruction {
BitField<47, 2, IsberdShift> shift;
} isberd;
union {
BitField<48, 1, u64> signed_a;
BitField<38, 1, u64> is_byte_chunk_a;
BitField<36, 2, VmadType> type_a;
BitField<36, 2, u64> byte_height_a;
BitField<49, 1, u64> signed_b;
BitField<50, 1, u64> use_register_b;
BitField<30, 1, u64> is_byte_chunk_b;
BitField<28, 2, VmadType> type_b;
BitField<28, 2, u64> byte_height_b;
BitField<51, 2, VmadShr> shr;
BitField<55, 1, u64> saturate; // Saturates the result (a * b + c)
BitField<47, 1, u64> cc;
} vmad;
union {
BitField<20, 16, u64> imm20_16;
BitField<36, 1, u64> product_shift_left;
@@ -1083,6 +1117,7 @@ public:
IPA,
OUT_R, // Emit vertex/primitive
ISBERD,
VMAD,
FFMA_IMM, // Fused Multiply and Add
FFMA_CR,
FFMA_RC,
@@ -1320,6 +1355,7 @@ private:
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"),
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),

View File

@@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
return gpu_addr;
}
GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
for (const auto& region : mapped_regions) {
const GPUVAddr region_end{region.gpu_addr + region.size};
if (region_start >= region.gpu_addr && region_start < region_end) {
return region_end;
}
}
return {};
}
boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
GPUVAddr gpu_addr = 0;
u64 free_space = 0;

View File

@@ -26,6 +26,7 @@ public:
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;

View File

@@ -11,32 +11,77 @@
#include "common/common_types.h"
#include "core/core.h"
#include "core/settings.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
class RasterizerCacheObject {
public:
/// Gets the address of the shader in guest memory, required for cache management
virtual VAddr GetAddr() const = 0;
/// Gets the size of the shader in guest memory, required for cache management
virtual std::size_t GetSizeInBytes() const = 0;
/// Wriets any cached resources back to memory
virtual void Flush() = 0;
/// Sets whether the cached object should be considered registered
void SetIsRegistered(bool registered) {
is_registered = registered;
}
/// Returns true if the cached object is registered
bool IsRegistered() const {
return is_registered;
}
/// Returns true if the cached object is dirty
bool IsDirty() const {
return is_dirty;
}
/// Returns ticks from when this cached object was last modified
u64 GetLastModifiedTicks() const {
return last_modified_ticks;
}
/// Marks an object as recently modified, used to specify whether it is clean or dirty
template <class T>
void MarkAsModified(bool dirty, T& cache) {
is_dirty = dirty;
last_modified_ticks = cache.GetModifiedTicks();
}
private:
bool is_registered{}; ///< Whether the object is currently registered with the cache
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
};
template <class T>
class RasterizerCache : NonCopyable {
friend class RasterizerCacheObject;
public:
/// Write any cached resources overlapping the specified region back to memory
void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
FlushObject(object);
}
}
/// Mark the specified region as being invalidated
void InvalidateRegion(VAddr addr, u64 size) {
if (size == 0)
return;
const ObjectInterval interval{addr, addr + size};
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
for (auto& cached_object : pair.second) {
if (!cached_object)
continue;
remove_objects.emplace(cached_object);
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
if (!object->IsRegistered()) {
// Skip duplicates
continue;
}
Unregister(object);
}
for (auto& remove_object : remove_objects) {
Unregister(remove_object);
}
remove_objects.clear();
}
/// Invalidates everything in the cache
@@ -62,6 +107,7 @@ protected:
/// Register an object into the cache
void Register(const T& object) {
object->SetIsRegistered(true);
object_cache.add({GetInterval(object), ObjectSet{object}});
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
@@ -69,12 +115,57 @@ protected:
/// Unregisters an object from the cache
void Unregister(const T& object) {
object->SetIsRegistered(false);
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
if (Settings::values.use_accurate_gpu_emulation) {
FlushObject(object);
}
object_cache.subtract({GetInterval(object), ObjectSet{object}});
}
/// Returns a ticks counter used for tracking when cached objects were last modified
u64 GetModifiedTicks() {
return ++modified_ticks;
}
private:
/// Returns a list of cached objects from the specified memory region, ordered by access time
std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
if (size == 0) {
return {};
}
std::vector<T> objects;
const ObjectInterval interval{addr, addr + size};
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
for (auto& cached_object : pair.second) {
if (!cached_object) {
continue;
}
objects.push_back(cached_object);
}
}
std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
});
return objects;
}
/// Flushes the specified object, updating appropriate cache state as needed
void FlushObject(const T& object) {
if (!object->IsDirty()) {
return;
}
object->Flush();
object->MarkAsModified(false, *this);
}
using ObjectSet = std::set<T>;
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
using ObjectInterval = typename ObjectCache::interval_type;
@@ -84,6 +175,6 @@ private:
object->GetAddr() + object->GetSizeInBytes());
}
ObjectCache object_cache;
ObjectSet remove_objects;
ObjectCache object_cache; ///< Cache of objects
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
};

View File

@@ -15,15 +15,18 @@
namespace OpenGL {
struct CachedBufferEntry final {
VAddr GetAddr() const {
struct CachedBufferEntry final : public RasterizerCacheObject {
VAddr GetAddr() const override {
return addr;
}
std::size_t GetSizeInBytes() const {
std::size_t GetSizeInBytes() const override {
return size;
}
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
VAddr addr;
std::size_t size;
GLintptr offset;

View File

@@ -286,7 +286,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
&ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
// Bind the buffer
glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo));
glBindBufferRange(GL_UNIFORM_BUFFER, static_cast<GLuint>(stage), buffer_cache.GetHandle(),
offset, static_cast<GLsizeiptr>(sizeof(ubo)));
Shader shader{shader_cache.GetStageProgram(program)};
@@ -423,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
Surface color_surface =
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
if (color_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even if
// the shader doesn't actually write to it.
color_surface->MarkAsModified(true, res_cache);
}
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
@@ -433,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
if (color_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even
// if the shader doesn't actually write to it.
color_surface->MarkAsModified(true, res_cache);
}
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
@@ -452,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
}
if (depth_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even if
// the shader doesn't actually write to it.
depth_surface->MarkAsModified(true, res_cache);
if (regs.stencil_enable) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
@@ -616,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
if (Settings::values.use_accurate_gpu_emulation) {
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
res_cache.FlushRegion(addr, size);
}
}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
@@ -626,6 +652,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
FlushRegion(addr, size);
InvalidateRegion(addr, size);
}

View File

@@ -34,18 +34,57 @@ struct FormatTuple {
bool compressed;
};
static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
auto& gpu{Core::System::GetInstance().GPU()};
const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
return cpu_addr ? *cpu_addr : 0;
static bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
return true;
default:
return false;
}
}
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
return {4, 4};
case PixelFormat::ASTC_2D_5X4:
return {5, 4};
case PixelFormat::ASTC_2D_8X8:
return {8, 8};
case PixelFormat::ASTC_2D_8X5:
return {8, 5};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
}
}
void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
addr = cpu_addr ? *cpu_addr : 0;
gpu_addr = gpu_addr_;
size_in_bytes = SizeInBytesRaw();
if (IsPixelFormatASTC(pixel_format)) {
// ASTC is uncompressed in software, in emulated as RGBA8
size_in_bytes_gl = width * height * depth * 4;
} else {
size_in_bytes_gl = SizeInBytesGL();
}
}
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.tic.Address());
params.is_tiled = config.tic.IsTiled();
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
params.pixel_format =
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value());
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
@@ -85,20 +124,23 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
break;
}
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = config.tic.max_mip_level + 1;
params.rt = {};
params.InitCacheParameters(config.tic.Address());
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) {
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.Address());
params.is_tiled = true;
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
params.is_tiled =
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << config.memory_layout.block_width;
params.block_height = 1 << config.memory_layout.block_height;
params.block_depth = 1 << config.memory_layout.block_depth;
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
@@ -107,26 +149,30 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
// Render target specific parameters, not used for caching
params.rt.index = static_cast<u32>(index);
params.rt.array_mode = config.array_mode;
params.rt.layer_stride = config.layer_stride;
params.rt.volume = config.volume;
params.rt.base_layer = config.base_layer;
params.InitCacheParameters(config.Address());
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
Tegra::GPUVAddr zeta_address,
Tegra::DepthFormat format) {
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(zeta_address);
params.is_tiled = true;
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << std::min(block_width, 5U);
params.block_height = 1 << std::min(block_height, 5U);
params.block_depth = 1 << std::min(block_depth, 5U);
params.pixel_format = PixelFormatFromDepthFormat(format);
params.component_type = ComponentTypeFromDepthFormat(format);
params.type = GetFormatType(params.pixel_format);
@@ -135,20 +181,22 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = zeta_height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
params.InitCacheParameters(zeta_address);
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.Address());
params.is_tiled = !config.linear;
params.block_height = params.is_tiled ? config.BlockHeight() : 0,
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
@@ -157,11 +205,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
params.InitCacheParameters(config.Address());
return params;
}
@@ -221,6 +269,8 @@ 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
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4
// Depth formats
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
@@ -264,28 +314,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
static bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8:
return true;
default:
return false;
}
}
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
return {4, 4};
case PixelFormat::ASTC_2D_8X8:
return {8, 8};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
}
}
MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
u32 actual_height{unaligned_height};
if (IsPixelFormatASTC(pixel_format)) {
@@ -313,29 +341,30 @@ static bool IsFormatBCn(PixelFormat format) {
}
template <bool morton_to_gl, PixelFormat format>
void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, std::size_t gl_buffer_size,
VAddr addr) {
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer,
std::size_t gl_buffer_size, VAddr addr) {
constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format);
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
// pixel values.
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
if (morton_to_gl) {
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
// pixel values.
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
addr, tile_size, bytes_per_pixel, stride, height, block_height);
addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth);
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
memcpy(gl_buffer, data.data(), size_to_copy);
} else {
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
// check the configuration for this and perform more generic un/swizzle
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
Memory::GetPointer(addr), gl_buffer, morton_to_gl);
std::vector<u8> data(gl_buffer_size);
Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth,
bytes_per_pixel, bytes_per_pixel, data.data(), gl_buffer,
false, block_height, block_depth);
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
memcpy(Memory::GetPointer(addr), data.data(), size_to_copy);
}
}
static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
SurfaceParams::MaxPixelFormat>
morton_to_gl_fns = {
// clang-format off
@@ -385,6 +414,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<true, PixelFormat::RG32UI>,
MortonCopy<true, PixelFormat::R32UI>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z24S8>,
@@ -393,7 +424,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
// clang-format on
};
static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
SurfaceParams::MaxPixelFormat>
gl_to_morton_fns = {
// clang-format off
@@ -410,17 +441,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<false, PixelFormat::RGBA16UI>,
MortonCopy<false, PixelFormat::R11FG11FB10F>,
MortonCopy<false, PixelFormat::RGBA32UI>,
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
// formats are not supported
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::DXT1>,
MortonCopy<false, PixelFormat::DXT23>,
MortonCopy<false, PixelFormat::DXT45>,
MortonCopy<false, PixelFormat::DXN1>,
MortonCopy<false, PixelFormat::DXN2UNORM>,
MortonCopy<false, PixelFormat::DXN2SNORM>,
MortonCopy<false, PixelFormat::BC7U>,
MortonCopy<false, PixelFormat::BC6H_UF16>,
MortonCopy<false, PixelFormat::BC6H_SF16>,
// TODO(Subv): Swizzling ASTC formats are not supported
nullptr,
MortonCopy<false, PixelFormat::G8R8U>,
MortonCopy<false, PixelFormat::G8R8S>,
@@ -445,6 +475,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<false, PixelFormat::RG32UI>,
MortonCopy<false, PixelFormat::R32UI>,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z24S8>,
@@ -604,22 +636,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type);
auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type);
std::size_t buffer_size =
std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total);
std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr);
static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
} else {
glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format,
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total),
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes),
nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) {
if (src_params.size_in_bytes < dst_params.size_in_bytes) {
// Upload the rest of the memory.
if (dst_params.is_tiled) {
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
@@ -629,12 +660,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
"reinterpretation but the texture is tiled.");
}
std::size_t remaining_size =
dst_params.size_in_bytes_total - src_params.size_in_bytes_total;
std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
std::vector<u8> data(remaining_size);
Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(),
data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size,
std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes),
data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
data.data());
}
@@ -680,7 +711,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
}
CachedSurface::CachedSurface(const SurfaceParams& params)
: params(params), gl_target(SurfaceTargetToGL(params.target)) {
: params(params), gl_target(SurfaceTargetToGL(params.target)),
cached_size_in_bytes(params.size_in_bytes) {
texture.Create();
const auto& rect{params.GetRect()};
@@ -730,9 +762,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
SurfaceParams::SurfaceTargetName(params.target));
// Clamp size to mapped GPU memory region
// TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
// R32F render buffer. We do not yet know if this is a game bug or something else, but this
// check is necessary to prevent flushing from overwriting unmapped memory.
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
if (cached_size_in_bytes > max_size) {
LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
cached_size_in_bytes = max_size;
}
}
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) {
union S8Z24 {
BitField<0, 24, u32> z24;
BitField<24, 8, u32> s8;
@@ -745,22 +789,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
};
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
S8Z24 input_pixel{};
Z24S8 output_pixel{};
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
S8Z24 s8z24_pixel{};
Z24S8 z24s8_pixel{};
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
output_pixel.s8.Assign(input_pixel.s8);
output_pixel.z24.Assign(input_pixel.z24);
std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
if (reverse) {
std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
s8z24_pixel.s8.Assign(z24s8_pixel.s8);
s8z24_pixel.z24.Assign(z24s8_pixel.z24);
std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
} else {
std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
z24s8_pixel.s8.Assign(s8z24_pixel.s8);
z24s8_pixel.z24.Assign(s8z24_pixel.z24);
std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
}
}
}
}
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
@@ -780,7 +831,9 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
u32 width, u32 height) {
switch (pixel_format) {
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8: {
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
case PixelFormat::ASTC_2D_5X4: {
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
u32 block_width{};
u32 block_height{};
@@ -790,7 +843,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
case PixelFormat::S8Z24:
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
ConvertS8Z24ToZ24S8(data, width, height);
ConvertS8Z24ToZ24S8(data, width, height, false);
break;
case PixelFormat::G8R8U:
@@ -801,49 +854,71 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
}
/**
* Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to
* Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or
* with typical desktop GPUs.
*/
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
u32 width, u32 height) {
switch (pixel_format) {
case PixelFormat::G8R8U:
case PixelFormat::G8R8S:
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8: {
LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
static_cast<u32>(pixel_format));
UNREACHABLE();
break;
}
case PixelFormat::S8Z24:
// Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24.
ConvertS8Z24ToZ24S8(data, width, height, true);
break;
}
}
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
void CachedSurface::LoadGLBuffer() {
ASSERT(params.type != SurfaceType::Fill);
const u8* const texture_src_data = Memory::GetPointer(params.addr);
ASSERT(texture_src_data);
const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
const u32 copy_size = params.width * params.height * bytes_per_pixel;
const std::size_t total_size = copy_size * params.depth;
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
gl_buffer.resize(params.size_in_bytes_gl);
if (params.is_tiled) {
gl_buffer.resize(total_size);
u32 depth = params.depth;
u32 block_depth = params.block_depth;
// TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do
// this for 3D textures, etc.
switch (params.target) {
case SurfaceParams::SurfaceTarget::Texture2D:
// Pass impl. to the fallback code below
break;
case SurfaceParams::SurfaceTarget::Texture2DArray:
case SurfaceParams::SurfaceTarget::TextureCubemap:
for (std::size_t index = 0; index < params.depth; ++index) {
const std::size_t offset{index * copy_size};
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, gl_buffer.data() + offset,
copy_size, params.addr + offset);
}
break;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}",
static_cast<u32>(params.target));
UNREACHABLE();
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
params.block_width, static_cast<u32>(params.target));
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
depth = 1U;
block_depth = 1U;
}
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, gl_buffer.data(), copy_size,
params.addr);
if (params.target == SurfaceParams::SurfaceTarget::TextureCubemap) {
// TODO(Blinkhawk): Figure where this number comes from and if it's constant or depends
// on the objects size and/or address.
u64 magic_number = 0x6000;
u64 offset = 0;
u64 offset_gl = 0;
u64 size = params.SizeInBytesCubeFace();
u64 gl_size = params.SizeInBytesCubeFaceGL();
for (u32 i = 0; i < depth; i++) {
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, 1,
gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
offset += size + magic_number;
offset_gl += gl_size;
}
} else {
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, depth,
gl_buffer.data(), gl_buffer.size(), params.addr);
}
} else {
const u8* const texture_src_data_end{texture_src_data + total_size};
const auto texture_src_data{Memory::GetPointer(params.addr)};
const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
gl_buffer.assign(texture_src_data, texture_src_data_end);
}
@@ -852,7 +927,45 @@ void CachedSurface::LoadGLBuffer() {
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer() {
ASSERT_MSG(false, "Unimplemented");
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
// OpenGL temporary buffer needs to be big enough to store raw texture size
gl_buffer.resize(GetSizeInBytes());
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
ASSERT(!tuple.compressed);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(),
gl_buffer.data());
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
params.height);
ASSERT(params.type != SurfaceType::Fill);
const u8* const texture_src_data = Memory::GetPointer(params.addr);
ASSERT(texture_src_data);
if (params.is_tiled) {
u32 depth = params.depth;
u32 block_depth = params.block_depth;
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
params.block_width, static_cast<u32>(params.target));
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
depth = 1U;
block_depth = 1U;
}
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
gl_buffer.size(), GetAddr());
} else {
std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes());
}
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
@@ -862,9 +975,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
MICROPROFILE_SCOPE(OpenGL_TextureUL);
ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height *
GetGLBytesPerPixel(params.pixel_format) * params.depth);
const auto& rect{params.GetRect()};
// Load data from memory to the surface
@@ -873,7 +983,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
std::size_t buffer_offset =
static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width +
static_cast<std::size_t>(x0)) *
GetGLBytesPerPixel(params.pixel_format);
SurfaceParams::GetBytesPerPixel(params.pixel_format);
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
const GLuint target_tex = texture.handle;
@@ -889,7 +999,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
glActiveTexture(GL_TEXTURE0);
@@ -899,7 +1009,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::Texture3D:
case SurfaceParams::SurfaceTarget::Texture2DArray:
@@ -907,16 +1017,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
static_cast<GLsizei>(params.depth), 0,
static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::TextureCubemap:
for (std::size_t face = 0; face < params.depth; ++face) {
glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face),
0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d),
static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()),
&gl_buffer[buffer_offset]);
buffer_offset += params.size_in_bytes_2d;
buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -926,7 +1036,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
}
} else {
@@ -955,7 +1065,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
&gl_buffer[buffer_offset]);
buffer_offset += params.size_in_bytes_2d;
buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -989,7 +1099,9 @@ Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) {
}
SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer(
regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)};
regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format,
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
return GetSurface(depth_params, preserve_contents);
}
@@ -1015,10 +1127,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
surface->LoadGLBuffer();
surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
}
void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
surface->FlushGLBuffer();
surface->MarkAsModified(false, *this);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
@@ -1035,8 +1144,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
} else if (preserve_contents) {
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
Unregister(surface);
Surface new_surface{RecreateSurface(surface, params)};
Unregister(surface);
Register(new_surface);
return new_surface;
} else {
@@ -1087,6 +1196,14 @@ void RasterizerCacheOpenGL::FermiCopySurface(
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
}
void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
const Surface& dst_surface) {
const auto& src_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
FlushRegion(src_params.addr, dst_params.size_in_bytes);
LoadSurface(dst_surface);
}
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
@@ -1095,6 +1212,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// Get a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{GetUncachedSurface(new_params)};
// With use_accurate_gpu_emulation enabled, do an accurate surface copy
if (Settings::values.use_accurate_gpu_emulation) {
AccurateCopySurface(old_surface, new_surface);
return new_surface;
}
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
if (old_params.target == new_params.target && old_params.type == new_params.type &&
old_params.depth == new_params.depth && old_params.depth == 1 &&
@@ -1106,11 +1229,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// If the format is the same, just do a framebuffer blit. This is significantly faster than
// using PBOs. The is also likely less accurate, as textures will be converted rather than
// reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
// reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate
// surface copy, where pixels are reinterpreted as a new format (without conversion). This
// code path uses OpenGL PBOs and is quite slow.
const bool is_blit{old_params.pixel_format == new_params.pixel_format ||
!Settings::values.use_accurate_framebuffers};
const bool is_blit{old_params.pixel_format == new_params.pixel_format};
switch (new_params.target) {
case SurfaceParams::SurfaceTarget::Texture2D:
@@ -1120,41 +1242,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
case SurfaceParams::SurfaceTarget::TextureCubemap: {
if (old_params.rt.array_mode != 1) {
// TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this
// yet (array rendering used as a cubemap texture).
LOG_CRITICAL(HW_GPU, "Unhandled rendertarget array_mode {}", old_params.rt.array_mode);
UNREACHABLE();
return new_surface;
}
// This seems to be used for render-to-cubemap texture
ASSERT_MSG(old_params.target == SurfaceParams::SurfaceTarget::Texture2D, "Unexpected");
ASSERT_MSG(old_params.pixel_format == new_params.pixel_format, "Unexpected");
ASSERT_MSG(old_params.rt.base_layer == 0, "Unimplemented");
// TODO(bunnei): Verify the below - this stride seems to be in 32-bit words, not pixels.
// Tested with Splatoon 2, Super Mario Odyssey, and Breath of the Wild.
const std::size_t byte_stride{old_params.rt.layer_stride * sizeof(u32)};
for (std::size_t index = 0; index < new_params.depth; ++index) {
Surface face_surface{TryGetReservedSurface(old_params)};
ASSERT_MSG(face_surface, "Unexpected");
if (is_blit) {
BlitSurface(face_surface, new_surface, read_framebuffer.handle,
draw_framebuffer.handle, face_surface->GetSurfaceParams().rt.index,
new_params.rt.index, index);
} else {
CopySurface(face_surface, new_surface, copy_pbo.handle,
face_surface->GetSurfaceParams().rt.index, new_params.rt.index, index);
}
old_params.addr += byte_stride;
}
case SurfaceParams::SurfaceTarget::TextureCubemap:
case SurfaceParams::SurfaceTarget::Texture3D:
AccurateCopySurface(old_surface, new_surface);
break;
}
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(new_params.target));

View File

@@ -18,6 +18,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
@@ -74,19 +75,21 @@ struct SurfaceParams {
RG32UI = 43,
R32UI = 44,
ASTC_2D_8X8 = 45,
ASTC_2D_8X5 = 46,
ASTC_2D_5X4 = 47,
MaxColorFormat,
// Depth formats
Z32F = 46,
Z16 = 47,
Z32F = 48,
Z16 = 49,
MaxDepthFormat,
// DepthStencil formats
Z24S8 = 48,
S8Z24 = 49,
Z32FS8 = 50,
Z24S8 = 50,
S8Z24 = 51,
Z32FS8 = 52,
MaxDepthStencilFormat,
@@ -129,6 +132,8 @@ struct SurfaceParams {
case Tegra::Texture::TextureType::Texture2D:
case Tegra::Texture::TextureType::Texture2DNoMipmap:
return SurfaceTarget::Texture2D;
case Tegra::Texture::TextureType::Texture3D:
return SurfaceTarget::Texture3D;
case Tegra::Texture::TextureType::TextureCubemap:
return SurfaceTarget::TextureCubemap;
case Tegra::Texture::TextureType::Texture1DArray:
@@ -220,6 +225,8 @@ struct SurfaceParams {
1, // RG32UI
1, // R32UI
4, // ASTC_2D_8X8
4, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // Z32F
1, // Z16
1, // Z24S8
@@ -282,6 +289,8 @@ struct SurfaceParams {
64, // RG32UI
32, // R32UI
16, // ASTC_2D_8X8
32, // ASTC_2D_8X5
32, // ASTC_2D_5X4
32, // Z32F
16, // Z16
32, // Z24S8
@@ -553,8 +562,12 @@ struct SurfaceParams {
return PixelFormat::BC6H_SF16;
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
return PixelFormat::ASTC_2D_4X4;
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
return PixelFormat::ASTC_2D_5X4;
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
return PixelFormat::ASTC_2D_8X8;
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
return PixelFormat::ASTC_2D_8X5;
case Tegra::Texture::TextureFormat::R16_G16:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
@@ -691,21 +704,42 @@ struct SurfaceParams {
return SurfaceType::Invalid;
}
/// Returns the sizer in bytes of the specified pixel format
static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
if (pixel_format == SurfaceParams::PixelFormat::Invalid) {
return 0;
}
return GetFormatBpp(pixel_format) / CHAR_BIT;
}
/// Returns the rectangle corresponding to this surface
MathUtil::Rectangle<u32> GetRect() const;
/// Returns the size of this surface as a 2D texture in bytes, adjusted for compression
std::size_t SizeInBytes2D() const {
/// Returns the total size of this surface in bytes, adjusted for compression
std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
const u32 compression_factor{GetCompressionFactor(pixel_format)};
ASSERT(width % compression_factor == 0);
ASSERT(height % compression_factor == 0);
return (width / compression_factor) * (height / compression_factor) *
GetFormatBpp(pixel_format) / CHAR_BIT;
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
const size_t uncompressed_size{
Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
height, depth, block_height, block_depth)};
// Divide by compression_factor^2, as height and width are factored by this
return uncompressed_size / (compression_factor * compression_factor);
}
/// Returns the total size of this surface in bytes, adjusted for compression
std::size_t SizeInBytesTotal() const {
return SizeInBytes2D() * depth;
/// Returns the size of this surface as an OpenGL texture in bytes
std::size_t SizeInBytesGL() const {
return SizeInBytesRaw(true);
}
/// Returns the size of this surface as a cube face in bytes
std::size_t SizeInBytesCubeFace() const {
return size_in_bytes / 6;
}
/// Returns the size of this surface as an OpenGL cube face in bytes
std::size_t SizeInBytesCubeFaceGL() const {
return size_in_bytes_gl / 6;
}
/// Creates SurfaceParams from a texture configuration
@@ -716,9 +750,10 @@ struct SurfaceParams {
static SurfaceParams CreateForFramebuffer(std::size_t index);
/// Creates SurfaceParams for a depth buffer configuration
static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
Tegra::GPUVAddr zeta_address,
Tegra::DepthFormat format);
static SurfaceParams CreateForDepthBuffer(
u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
/// Creates SurfaceParams for a Fermi2D surface copy
static SurfaceParams CreateForFermiCopySurface(
@@ -731,9 +766,13 @@ struct SurfaceParams {
other.depth);
}
VAddr addr;
/// Initializes parameters for caching, should be called after everything has been initialized
void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
bool is_tiled;
u32 block_width;
u32 block_height;
u32 block_depth;
PixelFormat pixel_format;
ComponentType component_type;
SurfaceType type;
@@ -741,15 +780,20 @@ struct SurfaceParams {
u32 height;
u32 depth;
u32 unaligned_height;
std::size_t size_in_bytes_total;
std::size_t size_in_bytes_2d;
SurfaceTarget target;
u32 max_mip_level;
// Parameters used for caching
VAddr addr;
Tegra::GPUVAddr gpu_addr;
std::size_t size_in_bytes;
std::size_t size_in_bytes_gl;
// Render target specific parameters, not used in caching
struct {
u32 index;
u32 array_mode;
u32 volume;
u32 layer_stride;
u32 base_layer;
} rt;
@@ -762,7 +806,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
SurfaceReserveKey res;
res.state = params;
res.state.rt = {}; // Ignore rt config in caching
res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
res.state.rt = {}; // Ignore rt config in caching
return res;
}
};
@@ -777,16 +822,20 @@ struct hash<SurfaceReserveKey> {
namespace OpenGL {
class CachedSurface final {
class CachedSurface final : public RasterizerCacheObject {
public:
CachedSurface(const SurfaceParams& params);
VAddr GetAddr() const {
VAddr GetAddr() const override {
return params.addr;
}
std::size_t GetSizeInBytes() const {
return params.size_in_bytes_total;
std::size_t GetSizeInBytes() const override {
return cached_size_in_bytes;
}
void Flush() override {
FlushGLBuffer();
}
const OGLTexture& Texture() const {
@@ -797,13 +846,6 @@ public:
return gl_target;
}
static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
if (format == SurfaceParams::PixelFormat::Invalid)
return 0;
return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
}
const SurfaceParams& GetSurfaceParams() const {
return params;
}
@@ -820,6 +862,7 @@ private:
std::vector<u8> gl_buffer;
SurfaceParams params;
GLenum gl_target;
std::size_t cached_size_in_bytes;
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -836,9 +879,6 @@ public:
/// Get the color surface based on the framebuffer configuration and the specified render target
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
/// Flushes the surface to Switch memory
void FlushSurface(const Surface& surface);
/// Tries to find a framebuffer using on the provided CPU address
Surface TryFindFramebufferSurface(VAddr addr) const;
@@ -862,6 +902,9 @@ private:
/// Tries to get a reserved surface for the specified parameters
Surface TryGetReservedSurface(const SurfaceParams& params);
/// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
/// 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.

View File

@@ -19,20 +19,21 @@ class CachedShader;
using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
class CachedShader final {
class CachedShader final : public RasterizerCacheObject {
public:
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
/// Gets the address of the shader in guest memory, required for cache management
VAddr GetAddr() const {
VAddr GetAddr() const override {
return addr;
}
/// Gets the size of the shader in guest memory, required for cache management
std::size_t GetSizeInBytes() const {
std::size_t GetSizeInBytes() const override {
return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64);
}
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
/// Gets the shader entries for the shader
const GLShader::ShaderEntries& GetShaderEntries() const {
return entries;

View File

@@ -1142,6 +1142,7 @@ private:
case Tegra::Shader::TextureType::Texture2D: {
return 2;
}
case Tegra::Shader::TextureType::Texture3D:
case Tegra::Shader::TextureType::TextureCube: {
return 3;
}
@@ -1436,7 +1437,6 @@ private:
break;
}
case OpCode::Type::Shift: {
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true);
std::string op_b;
@@ -1478,7 +1478,6 @@ private:
}
break;
}
case OpCode::Type::ArithmeticIntegerImmediate: {
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
std::string op_b = std::to_string(instr.alu.imm20_32.Value());
@@ -2626,14 +2625,14 @@ private:
const std::string pred =
GetPredicateCondition(instr.csetp.pred39, instr.csetp.neg_pred39 != 0);
const std::string combiner = GetPredicateCombiner(instr.csetp.op);
const std::string controlCode = regs.GetControlCode(instr.csetp.cc);
const std::string control_code = regs.GetControlCode(instr.csetp.cc);
if (instr.csetp.pred3 != static_cast<u64>(Pred::UnusedIndex)) {
SetPredicate(instr.csetp.pred3,
'(' + controlCode + ") " + combiner + " (" + pred + ')');
'(' + control_code + ") " + combiner + " (" + pred + ')');
}
if (instr.csetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
SetPredicate(instr.csetp.pred0,
"!(" + controlCode + ") " + combiner + " (" + pred + ')');
"!(" + control_code + ") " + combiner + " (" + pred + ')');
}
break;
}
@@ -2953,6 +2952,88 @@ private:
LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed");
break;
}
case OpCode::Id::VMAD: {
const bool signed_a = instr.vmad.signed_a == 1;
const bool signed_b = instr.vmad.signed_b == 1;
const bool result_signed = signed_a || signed_b;
boost::optional<std::string> forced_result;
auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed,
Tegra::Shader::VmadType type, u64 byte_height) {
const std::string value = [&]() {
if (!is_chunk) {
const auto offset = static_cast<u32>(byte_height * 8);
return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)";
}
const std::string zero = "0";
switch (type) {
case Tegra::Shader::VmadType::Size16_Low:
return '(' + op + " & 0xffff)";
case Tegra::Shader::VmadType::Size16_High:
return '(' + op + " >> 16)";
case Tegra::Shader::VmadType::Size32:
// TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when
// this type is used (1 * 1 + 0 == 0x5b800000). Until a better
// explanation is found: assert.
UNREACHABLE_MSG("Unimplemented");
return zero;
case Tegra::Shader::VmadType::Invalid:
// Note(Rodrigo): This flag is invalid according to nvdisasm. From my
// testing (even though it's invalid) this makes the whole instruction
// assign zero to target register.
forced_result = boost::make_optional(zero);
return zero;
default:
UNREACHABLE();
return zero;
}
}();
if (is_signed) {
return "int(" + value + ')';
}
return value;
};
const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false),
instr.vmad.is_byte_chunk_a != 0, signed_a,
instr.vmad.type_a, instr.vmad.byte_height_a);
std::string op_b;
if (instr.vmad.use_register_b) {
op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b,
instr.vmad.byte_height_b);
} else {
op_b = '(' +
std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16())
: instr.alu.GetImm20_16()) +
')';
}
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed);
std::string result;
if (forced_result) {
result = *forced_result;
} else {
result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
switch (instr.vmad.shr) {
case Tegra::Shader::VmadShr::Shr7:
result = '(' + result + " >> 7)";
break;
case Tegra::Shader::VmadShr::Shr15:
result = '(' + result + " >> 15)";
break;
}
}
regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1,
instr.vmad.saturate == 1, 0, Register::Size::Word,
instr.vmad.cc);
break;
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
UNREACHABLE();

View File

@@ -4,6 +4,7 @@
#include <cmath>
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
#include "core/memory.h"
#include "video_core/gpu.h"
@@ -39,72 +40,146 @@ struct alignas(64) SwizzleTable {
constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>();
constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>();
static void LegacySwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
u32 block_height) {
/**
* This function manages ALL the GOBs(Group of Bytes) Inside a single block.
* Instead of going gob by gob, we map the coordinates inside a block and manage from
* those. Block_Width is assumed to be 1.
*/
void PreciseProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
const u32 y_end, const u32 z_end, const u32 tile_offset,
const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
std::array<u8*, 2> data_ptrs;
const std::size_t stride = width * bytes_per_pixel;
const std::size_t gobs_in_x = 64;
const std::size_t gobs_in_y = 8;
const std::size_t gobs_size = gobs_in_x * gobs_in_y;
const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
for (std::size_t y = 0; y < height; ++y) {
const std::size_t gob_y_address =
(y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
(y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
const auto& table = legacy_swizzle_table[y % gobs_in_y];
for (std::size_t x = 0; x < width; ++x) {
const std::size_t gob_address =
gob_y_address + (x * bytes_per_pixel / gobs_in_x) * gobs_size * block_height;
const std::size_t x2 = x * bytes_per_pixel;
const std::size_t swizzle_offset = gob_address + table[x2 % gobs_in_x];
const std::size_t pixel_index = (x + y * width) * out_bytes_per_pixel;
u32 z_address = tile_offset;
const u32 gob_size_x = 64;
const u32 gob_size_y = 8;
const u32 gob_size_z = 1;
const u32 gob_size = gob_size_x * gob_size_y * gob_size_z;
for (u32 z = z_start; z < z_end; z++) {
u32 y_address = z_address;
u32 pixel_base = layer_z * z + y_start * stride_x;
for (u32 y = y_start; y < y_end; y++) {
const auto& table = legacy_swizzle_table[y % gob_size_y];
for (u32 x = x_start; x < x_end; x++) {
const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % gob_size_x]};
const u32 pixel_index{x * out_bytes_per_pixel + pixel_base};
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
}
pixel_base += stride_x;
if ((y + 1) % gob_size_y == 0)
y_address += gob_size;
}
z_address += xy_block_size;
}
}
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
/**
* This function manages ALL the GOBs(Group of Bytes) Inside a single block.
* Instead of going gob by gob, we map the coordinates inside a block and manage from
* those. Block_Width is assumed to be 1.
*/
void FastProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
const u32 y_end, const u32 z_end, const u32 tile_offset,
const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
std::array<u8*, 2> data_ptrs;
u32 z_address = tile_offset;
const u32 x_startb = x_start * bytes_per_pixel;
const u32 x_endb = x_end * bytes_per_pixel;
const u32 copy_size = 16;
const u32 gob_size_x = 64;
const u32 gob_size_y = 8;
const u32 gob_size_z = 1;
const u32 gob_size = gob_size_x * gob_size_y * gob_size_z;
for (u32 z = z_start; z < z_end; z++) {
u32 y_address = z_address;
u32 pixel_base = layer_z * z + y_start * stride_x;
for (u32 y = y_start; y < y_end; y++) {
const auto& table = fast_swizzle_table[y % gob_size_y];
for (u32 xb = x_startb; xb < x_endb; xb += copy_size) {
const u32 swizzle_offset{y_address + table[(xb / copy_size) % 4]};
const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
const u32 pixel_index{out_x + pixel_base};
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
std::memcpy(data_ptrs[0], data_ptrs[1], copy_size);
}
pixel_base += stride_x;
if ((y + 1) % gob_size_y == 0)
y_address += gob_size;
}
z_address += xy_block_size;
}
}
std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
/**
* This function unswizzles or swizzles a texture by mapping Linear to BlockLinear Textue.
* The body of this function takes care of splitting the swizzled texture into blocks,
* and managing the extents of it. Once all the parameters of a single block are obtained,
* the function calls 'ProcessBlock' to process that particular Block.
*
* Documentation for the memory layout and decoding can be found at:
* https://envytools.readthedocs.io/en/latest/hw/memory/g80-surface.html#blocklinear-surfaces
*/
template <bool fast>
void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, const u32 width,
const u32 height, const u32 depth, const u32 bytes_per_pixel,
const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth) {
auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
const u32 stride_x = width * out_bytes_per_pixel;
const u32 layer_z = height * stride_x;
const u32 gob_x_bytes = 64;
const u32 gob_elements_x = gob_x_bytes / bytes_per_pixel;
const u32 gob_elements_y = 8;
const u32 gob_elements_z = 1;
const u32 block_x_elements = gob_elements_x;
const u32 block_y_elements = gob_elements_y * block_height;
const u32 block_z_elements = gob_elements_z * block_depth;
const u32 blocks_on_x = div_ceil(width, block_x_elements);
const u32 blocks_on_y = div_ceil(height, block_y_elements);
const u32 blocks_on_z = div_ceil(depth, block_z_elements);
const u32 blocks = blocks_on_x * blocks_on_y * blocks_on_z;
const u32 gob_size = gob_x_bytes * gob_elements_y * gob_elements_z;
const u32 xy_block_size = gob_size * block_height;
const u32 block_size = xy_block_size * block_depth;
u32 tile_offset = 0;
for (u32 zb = 0; zb < blocks_on_z; zb++) {
const u32 z_start = zb * block_z_elements;
const u32 z_end = std::min(depth, z_start + block_z_elements);
for (u32 yb = 0; yb < blocks_on_y; yb++) {
const u32 y_start = yb * block_y_elements;
const u32 y_end = std::min(height, y_start + block_y_elements);
for (u32 xb = 0; xb < blocks_on_x; xb++) {
const u32 x_start = xb * block_x_elements;
const u32 x_end = std::min(width, x_start + block_x_elements);
if (fast) {
FastProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
} else {
PreciseProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
}
tile_offset += block_size;
}
}
}
}
static void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
u32 block_height) {
std::array<u8*, 2> data_ptrs;
const std::size_t stride{width * bytes_per_pixel};
const std::size_t gobs_in_x = 64;
const std::size_t gobs_in_y = 8;
const std::size_t gobs_size = gobs_in_x * gobs_in_y;
const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
const std::size_t copy_size{16};
for (std::size_t y = 0; y < height; ++y) {
const std::size_t initial_gob =
(y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
(y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
const std::size_t pixel_base{y * width * out_bytes_per_pixel};
const auto& table = fast_swizzle_table[y % gobs_in_y];
for (std::size_t xb = 0; xb < stride; xb += copy_size) {
const std::size_t gob_address{initial_gob +
(xb / gobs_in_x) * gobs_size * block_height};
const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]};
const std::size_t out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
const std::size_t pixel_index{out_x + pixel_base};
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
std::memcpy(data_ptrs[0], data_ptrs[1], copy_size);
}
}
}
void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
bool unswizzle, u32 block_height, u32 block_depth) {
if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) {
FastSwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
unswizzled_data, unswizzle, block_height);
SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth);
} else {
LegacySwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
unswizzled_data, unswizzle, block_height);
SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth);
}
}
@@ -125,7 +200,9 @@ u32 BytesPerPixel(TextureFormat format) {
case TextureFormat::R32_G32_B32:
return 12;
case TextureFormat::ASTC_2D_4X4:
case TextureFormat::ASTC_2D_5X4:
case TextureFormat::ASTC_2D_8X8:
case TextureFormat::ASTC_2D_8X5:
case TextureFormat::A8R8G8B8:
case TextureFormat::A2B10G10R10:
case TextureFormat::BF10GF11RF11:
@@ -152,10 +229,11 @@ u32 BytesPerPixel(TextureFormat format) {
}
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
u32 height, u32 block_height) {
std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel,
Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
u32 height, u32 depth, u32 block_height, u32 block_depth) {
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
CopySwizzledData(width / tile_size, height / tile_size, depth, bytes_per_pixel, bytes_per_pixel,
Memory::GetPointer(address), unswizzled_data.data(), true, block_height,
block_depth);
return unswizzled_data;
}
@@ -199,4 +277,19 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
return rgba_data;
}
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth) {
if (tiled) {
const u32 gobs_in_x = 64 / bytes_per_pixel;
const u32 gobs_in_y = 8;
const u32 gobs_in_z = 1;
const u32 aligned_width = Common::AlignUp(width, gobs_in_x);
const u32 aligned_height = Common::AlignUp(height, gobs_in_y * block_height);
const u32 aligned_depth = Common::AlignUp(depth, gobs_in_z * block_depth);
return aligned_width * aligned_height * aligned_depth * bytes_per_pixel;
} else {
return width * height * depth * bytes_per_pixel;
}
}
} // namespace Tegra::Texture

View File

@@ -14,17 +14,14 @@ namespace Tegra::Texture {
* Unswizzles a swizzled texture without changing its format.
*/
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
u32 height, u32 block_height = TICEntry::DefaultBlockHeight);
/**
* Unswizzles a swizzled depth texture without changing its format.
*/
std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 width, u32 height,
u32 block_height = TICEntry::DefaultBlockHeight);
u32 height, u32 depth,
u32 block_height = TICEntry::DefaultBlockHeight,
u32 block_depth = TICEntry::DefaultBlockHeight);
/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary.
void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height);
void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
bool unswizzle, u32 block_height, u32 block_depth);
/**
* Decodes an unswizzled texture into a A8R8G8B8 texture.
@@ -32,4 +29,10 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height);
/**
* This function calculates the correct size of a texture depending if it's tiled or not.
*/
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth);
} // namespace Tegra::Texture

View File

@@ -141,6 +141,7 @@ static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
struct TICEntry {
static constexpr u32 DefaultBlockHeight = 16;
static constexpr u32 DefaultBlockDepth = 1;
union {
u32 raw;
@@ -161,7 +162,9 @@ struct TICEntry {
BitField<21, 3, TICHeaderVersion> header_version;
};
union {
BitField<0, 3, u32> block_width;
BitField<3, 3, u32> block_height;
BitField<6, 3, u32> block_depth;
// High 16 bits of the pitch value
BitField<0, 16, u32> pitch_high;
@@ -202,13 +205,24 @@ struct TICEntry {
return depth_minus_1 + 1;
}
u32 BlockWidth() const {
ASSERT(IsTiled());
// The block height is stored in log2 format.
return 1 << block_width;
}
u32 BlockHeight() const {
ASSERT(header_version == TICHeaderVersion::BlockLinear ||
header_version == TICHeaderVersion::BlockLinearColorKey);
ASSERT(IsTiled());
// The block height is stored in log2 format.
return 1 << block_height;
}
u32 BlockDepth() const {
ASSERT(IsTiled());
// The block height is stored in log2 format.
return 1 << block_depth;
}
bool IsTiled() const {
return header_version == TICHeaderVersion::BlockLinear ||
header_version == TICHeaderVersion::BlockLinearColorKey;

View File

@@ -2,96 +2,114 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <thread>
#include "common/assert.h"
#include <json.hpp>
#include "common/detached_tasks.h"
#include "common/web_result.h"
#include "web_service/telemetry_json.h"
#include "web_service/web_backend.h"
namespace WebService {
TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
const std::string& token)
: host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
struct TelemetryJson::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
nlohmann::json& TopSection() {
return sections[static_cast<u8>(Telemetry::FieldType::None)];
}
const nlohmann::json& TopSection() const {
return sections[static_cast<u8>(Telemetry::FieldType::None)];
}
template <class T>
void Serialize(Telemetry::FieldType type, const std::string& name, T value) {
sections[static_cast<u8>(type)][name] = value;
}
void SerializeSection(Telemetry::FieldType type, const std::string& name) {
TopSection()[name] = sections[static_cast<unsigned>(type)];
}
nlohmann::json output;
std::array<nlohmann::json, 7> sections;
std::string host;
std::string username;
std::string token;
};
TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token)
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
TelemetryJson::~TelemetryJson() = default;
template <class T>
void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
sections[static_cast<u8>(type)][name] = value;
}
void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
TopSection()[name] = sections[static_cast<unsigned>(type)];
}
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
}
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
Serialize(field.GetType(), field.GetName(), field.GetValue().count());
impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count());
}
void TelemetryJson::Complete() {
SerializeSection(Telemetry::FieldType::App, "App");
SerializeSection(Telemetry::FieldType::Session, "Session");
SerializeSection(Telemetry::FieldType::Performance, "Performance");
SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
impl->SerializeSection(Telemetry::FieldType::App, "App");
impl->SerializeSection(Telemetry::FieldType::Session, "Session");
impl->SerializeSection(Telemetry::FieldType::Performance, "Performance");
impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
auto content = TopSection().dump();
auto content = impl->TopSection().dump();
// Send the telemetry async but don't handle the errors since they were written to the log
Common::DetachedTasks::AddTask(
[host{this->host}, username{this->username}, token{this->token}, content]() {
[host{impl->host}, username{impl->username}, token{impl->token}, content]() {
Client{host, username, token}.PostJson("/telemetry", content, true);
});
}

View File

@@ -4,11 +4,9 @@
#pragma once
#include <array>
#include <chrono>
#include <string>
#include <json.hpp>
#include "common/telemetry.h"
#include "common/web_result.h"
namespace WebService {
@@ -18,8 +16,8 @@ namespace WebService {
*/
class TelemetryJson : public Telemetry::VisitorInterface {
public:
TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
~TelemetryJson();
TelemetryJson(std::string host, std::string username, std::string token);
~TelemetryJson() override;
void Visit(const Telemetry::Field<bool>& field) override;
void Visit(const Telemetry::Field<double>& field) override;
@@ -39,20 +37,8 @@ public:
void Complete() override;
private:
nlohmann::json& TopSection() {
return sections[static_cast<u8>(Telemetry::FieldType::None)];
}
template <class T>
void Serialize(Telemetry::FieldType type, const std::string& name, T value);
void SerializeSection(Telemetry::FieldType type, const std::string& name);
nlohmann::json output;
std::array<nlohmann::json, 7> sections;
std::string host;
std::string username;
std::string token;
struct Impl;
std::unique_ptr<Impl> impl;
};
} // namespace WebService

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <json.hpp>
#include "common/web_result.h"
#include "web_service/verify_login.h"
#include "web_service/web_backend.h"

View File

@@ -3,9 +3,11 @@
// Refer to the license.txt file included.
#include <cstdlib>
#include <mutex>
#include <string>
#include <thread>
#include <LUrlParser.h>
#include <httplib.h>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/web_result.h"
#include "core/settings.h"
@@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30;
Client::JWTCache Client::jwt_cache{};
Client::Client(const std::string& host, const std::string& username, const std::string& token)
: host(host), username(username), token(token) {
std::lock_guard<std::mutex> lock(jwt_cache.mutex);
if (username == jwt_cache.username && token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
}
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
const std::string& data, const std::string& jwt,
const std::string& username, const std::string& token) {
if (cli == nullptr) {
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
int port;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
cli =
std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
struct Client::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
std::lock_guard<std::mutex> lock(jwt_cache.mutex);
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
}
if (cli == nullptr) {
LOG_ERROR(WebService, "Invalid URL {}", host + path);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
/// A generic function handles POST, GET and DELETE request together
Common::WebResult GenericJson(const std::string& method, const std::string& path,
const std::string& data, bool allow_anonymous) {
if (jwt.empty()) {
UpdateJWT();
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
"Credentials needed"};
}
auto result = GenericJson(method, path, data, jwt);
if (result.result_string == "401") {
// Try again with new JWT
UpdateJWT();
result = GenericJson(method, path, data, jwt);
}
return result;
}
httplib::Headers params;
if (!jwt.empty()) {
params = {
{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
};
} else if (!username.empty()) {
params = {
{std::string("x-username"), username},
{std::string("x-token"), token},
/**
* A generic function with explicit authentication method specified
* JWT is used if the jwt parameter is not empty
* username + token is used if jwt is empty but username and token are not empty
* anonymous if all of jwt, username and token are empty
*/
Common::WebResult GenericJson(const std::string& method, const std::string& path,
const std::string& data, const std::string& jwt = "",
const std::string& username = "", const std::string& token = "") {
if (cli == nullptr) {
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
int port;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
}
}
if (cli == nullptr) {
LOG_ERROR(WebService, "Invalid URL {}", host + path);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
}
httplib::Headers params;
if (!jwt.empty()) {
params = {
{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
};
} else if (!username.empty()) {
params = {
{std::string("x-username"), username},
{std::string("x-token"), token},
};
}
params.emplace(std::string("api-version"),
std::string(API_VERSION.begin(), API_VERSION.end()));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
};
httplib::Request request;
request.method = method;
request.path = path;
request.headers = params;
request.body = data;
httplib::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
return Common::WebResult{Common::WebResult::Code::HttpError,
std::to_string(response.status)};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
}
if (content_type->second.find("application/json") == std::string::npos &&
content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
}
return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
}
params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
};
// Retrieve a new JWT from given username and token
void UpdateJWT() {
if (username.empty() || token.empty()) {
return;
}
httplib::Request request;
request.method = method;
request.path = path;
request.headers = params;
request.body = data;
httplib::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
return Common::WebResult{Common::WebResult::Code::HttpError,
std::to_string(response.status)};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
}
if (content_type->second.find("application/json") == std::string::npos &&
content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
}
return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
}
void Client::UpdateJWT() {
if (!username.empty() && !token.empty()) {
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
if (result.result_code != Common::WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
@@ -123,27 +158,39 @@ void Client::UpdateJWT() {
jwt_cache.jwt = jwt = result.returned_data;
}
}
std::string host;
std::string username;
std::string token;
std::string jwt;
std::unique_ptr<httplib::Client> cli;
struct JWTCache {
std::mutex mutex;
std::string username;
std::string token;
std::string jwt;
};
static inline JWTCache jwt_cache;
};
Client::Client(std::string host, std::string username, std::string token)
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
Client::~Client() = default;
Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return impl->GenericJson("POST", path, data, allow_anonymous);
}
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
const std::string& data, bool allow_anonymous) {
if (jwt.empty()) {
UpdateJWT();
}
Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
return impl->GenericJson("GET", path, "", allow_anonymous);
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
}
auto result = GenericJson(method, path, data, jwt);
if (result.result_string == "401") {
// Try again with new JWT
UpdateJWT();
result = GenericJson(method, path, data, jwt);
}
return result;
Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return impl->GenericJson("DELETE", path, data, allow_anonymous);
}
} // namespace WebService

View File

@@ -4,23 +4,19 @@
#pragma once
#include <functional>
#include <mutex>
#include <memory>
#include <string>
#include <tuple>
#include <httplib.h>
#include "common/common_types.h"
#include "common/web_result.h"
namespace httplib {
class Client;
namespace Common {
struct WebResult;
}
namespace WebService {
class Client {
public:
Client(const std::string& host, const std::string& username, const std::string& token);
Client(std::string host, std::string username, std::string token);
~Client();
/**
* Posts JSON to the specified path.
@@ -30,9 +26,7 @@ public:
* @return the result of the request.
*/
Common::WebResult PostJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return GenericJson("POST", path, data, allow_anonymous);
}
bool allow_anonymous);
/**
* Gets JSON from the specified path.
@@ -40,9 +34,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
return GenericJson("GET", path, "", allow_anonymous);
}
Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
/**
* Deletes JSON to the specified path.
@@ -52,41 +44,11 @@ public:
* @return the result of the request.
*/
Common::WebResult DeleteJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return GenericJson("DELETE", path, data, allow_anonymous);
}
bool allow_anonymous);
private:
/// A generic function handles POST, GET and DELETE request together
Common::WebResult GenericJson(const std::string& method, const std::string& path,
const std::string& data, bool allow_anonymous);
/**
* A generic function with explicit authentication method specified
* JWT is used if the jwt parameter is not empty
* username + token is used if jwt is empty but username and token are not empty
* anonymous if all of jwt, username and token are empty
*/
Common::WebResult GenericJson(const std::string& method, const std::string& path,
const std::string& data, const std::string& jwt = "",
const std::string& username = "", const std::string& token = "");
// Retrieve a new JWT from given username and token
void UpdateJWT();
std::string host;
std::string username;
std::string token;
std::string jwt;
std::unique_ptr<httplib::Client> cli;
struct JWTCache {
std::mutex mutex;
std::string username;
std::string token;
std::string jwt;
};
static JWTCache jwt_cache;
struct Impl;
std::unique_ptr<Impl> impl;
};
} // namespace WebService

View File

@@ -85,8 +85,8 @@ void Config::ReadValues() {
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
Settings::values.use_accurate_framebuffers =
qt_config->value("use_accurate_framebuffers", false).toBool();
Settings::values.use_accurate_gpu_emulation =
qt_config->value("use_accurate_gpu_emulation", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
@@ -233,7 +233,7 @@ void Config::SaveValues() {
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
qt_config->setValue("frame_limit", Settings::values.frame_limit);
qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers);
qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
// Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red);

View File

@@ -75,7 +75,7 @@ void ConfigureGraphics::setConfiguration() {
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setValue(Settings::values.frame_limit);
ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers);
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue);
ui->bg_button->setStyleSheet(
@@ -87,7 +87,7 @@ void ConfigureGraphics::applyConfiguration() {
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked();
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());

View File

@@ -50,9 +50,9 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_accurate_framebuffers">
<widget class="QCheckBox" name="use_accurate_gpu_emulation">
<property name="text">
<string>Use accurate framebuffers (slow)</string>
<string>Use accurate GPU emulation (slow)</string>
</property>
</widget>
</item>

View File

@@ -386,8 +386,9 @@ void GraphicsSurfaceWidget::OnUpdate() {
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
*address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height);
auto unswizzled_data =
Tegra::Texture::UnswizzleTexture(*address, 1, Tegra::Texture::BytesPerPixel(surface_format),
surface_width, surface_height, 1U);
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
surface_width, surface_height);

View File

@@ -66,10 +66,11 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
}
};
add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList());
add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList());
add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList());
add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList());
const auto& system = Core::System::GetInstance();
add_threads(system.Scheduler(0).GetThreadList());
add_threads(system.Scheduler(1).GetThreadList());
add_threads(system.Scheduler(2).GetThreadList());
add_threads(system.Scheduler(3).GetThreadList());
return item_list;
}

View File

@@ -96,7 +96,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
FileSys::ContentRecordType::Program);
for (const auto& game : installed_games) {
const auto& file = cache->GetEntryUnparsed(game);
const auto file = cache->GetEntryUnparsed(game);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
@@ -107,7 +107,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
loader->ReadProgramId(program_id);
const FileSys::PatchManager patch{program_id};
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
const auto control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
if (control != nullptr)
GetMetadataFromControlNCA(patch, *control, icon, name);
@@ -135,9 +135,10 @@ void GameListWorker::AddInstalledTitlesToGameList() {
FileSys::ContentRecordType::Control);
for (const auto& entry : control_data) {
const auto nca = cache->GetEntry(entry);
if (nca != nullptr)
nca_control_map.insert_or_assign(entry.title_id, nca);
auto nca = cache->GetEntry(entry);
if (nca != nullptr) {
nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
}
}
}
@@ -153,9 +154,11 @@ void GameListWorker::FillControlMap(const std::string& dir_path) {
QFileInfo file_info(physical_name.c_str());
if (!is_dir && file_info.suffix().toStdString() == "nca") {
auto nca =
std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (nca->GetType() == FileSys::NCAContentType::Control)
nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (nca->GetType() == FileSys::NCAContentType::Control) {
const u64 title_id = nca->GetTitleId();
nca_control_map.insert_or_assign(title_id, std::move(nca));
}
}
return true;
};

View File

@@ -63,7 +63,7 @@ private:
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
QStringList watch_list;
QString dir_path;
bool deep_scan;

View File

@@ -31,6 +31,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <QtGui>
#include <QtWidgets>
#include <fmt/format.h>
@@ -171,8 +172,11 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
show();
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
// Necessary to load titles from nand in gamelist.
Service::FileSystem::CreateFactories(vfs);
Service::FileSystem::CreateFactories(*vfs);
game_list->LoadCompatibilityList();
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
@@ -443,6 +447,8 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Help
connect(ui.action_Rederive, &QAction::triggered, this,
std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
}
@@ -902,22 +908,20 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
}
void GMainWindow::OnMenuLoadFile() {
QString extensions;
for (const auto& piece : game_list->supported_file_extensions)
extensions += "*." + piece + " ";
const QString extensions =
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
"%1 is an identifier for the Switch executable file extensions.")
.arg(extensions);
const QString filename = QFileDialog::getOpenFileName(
this, tr("Load File"), UISettings::values.roms_path, file_filter);
extensions += "main ";
QString file_filter = tr("Switch Executable") + " (" + extensions + ")";
file_filter += ";;" + tr("All Files (*.*)");
QString filename = QFileDialog::getOpenFileName(this, tr("Load File"),
UISettings::values.roms_path, file_filter);
if (!filename.isEmpty()) {
UISettings::values.roms_path = QFileInfo(filename).path();
BootGame(filename);
if (filename.isEmpty()) {
return;
}
UISettings::values.roms_path = QFileInfo(filename).path();
BootGame(filename);
}
void GMainWindow::OnMenuLoadFolder() {
@@ -1133,7 +1137,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
Service::FileSystem::CreateFactories(vfs);
Service::FileSystem::CreateFactories(*vfs);
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
}
@@ -1375,6 +1379,86 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
}
}
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
const auto res = QMessageBox::information(
this, tr("Confirm Key Rederivation"),
tr("You are about to force rederive all of your keys. \nIf you do not know what this "
"means or what you are doing, \nthis is a potentially destructive action. \nPlease "
"make "
"sure this is what you want \nand optionally make backups.\n\nThis will delete your "
"autogenerated key files and re-run the key derivation module."),
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
if (res == QMessageBox::Cancel)
return;
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
"prod.keys_autogenerated");
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
"console.keys_autogenerated");
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
"title.keys_autogenerated");
}
Core::Crypto::KeyManager keys{};
if (keys.BaseDeriveNecessary()) {
Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
Service::FileSystem::CreateFactories(*vfs);
keys.DeriveETicket(pdm);
};
QString errors;
if (!pdm.HasFuses())
errors += tr("- Missing fuses - Cannot derive SBK\n");
if (!pdm.HasBoot0())
errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
if (!pdm.HasPackage2())
errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
if (!pdm.HasProdInfo())
errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
if (!errors.isEmpty()) {
QMessageBox::warning(
this, tr("Warning Missing Derivation Components"),
tr("The following are missing from your configuration that may hinder key "
"derivation. It will be attempted but may not complete.<br><br>") +
errors +
tr("<br><br>You can get all of these and dump all of your games easily by "
"following <a href='https://yuzu-emu.org/help/quickstart/quickstart/'>the "
"quickstart guide</a>. Alternatively, you can use another method of dumping "
"to obtain all of your keys."));
}
QProgressDialog prog;
prog.setRange(0, 0);
prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
"system's performance."));
prog.setWindowTitle(tr("Deriving Keys"));
prog.show();
auto future = QtConcurrent::run(function);
while (!future.isFinished()) {
QCoreApplication::processEvents();
}
prog.close();
}
Service::FileSystem::CreateFactories(*vfs);
if (behavior == ReinitializeKeyBehavior::Warning) {
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
}
bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true;
@@ -1483,7 +1567,7 @@ void GMainWindow::UpdateUITheme() {
emit UpdateThemedIcons();
}
void GMainWindow::SetDiscordEnabled(bool state) {
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
#ifdef USE_DISCORD_PRESENCE
if (state) {
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();

View File

@@ -41,6 +41,11 @@ enum class EmulatedDirectoryTarget {
SDMC,
};
enum class ReinitializeKeyBehavior {
NoWarning,
Warning,
};
namespace DiscordRPC {
class DiscordInterface;
}
@@ -167,6 +172,7 @@ private slots:
void HideFullscreen();
void ToggleWindowMode();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
void UpdateStatusBar();

View File

@@ -103,6 +103,7 @@
</property>
<addaction name="action_Report_Compatibility"/>
<addaction name="separator"/>
<addaction name="action_Rederive"/>
<addaction name="action_About"/>
</widget>
<addaction name="menu_File"/>
@@ -159,6 +160,11 @@
<string>&amp;Stop</string>
</property>
</action>
<action name="action_Rederive">
<property name="text">
<string>Reinitialize keys...</string>
</property>
</action>
<action name="action_About">
<property name="text">
<string>About yuzu</string>

View File

@@ -99,8 +99,8 @@ void Config::ReadValues() {
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_accurate_framebuffers =
sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false);
Settings::values.use_accurate_gpu_emulation =
sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);

View File

@@ -110,9 +110,9 @@ use_frame_limit =
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
# Whether to use accurate framebuffers
# Whether to use accurate GPU emulation
# 0 (default): Off (fast), 1 : On (slow)
use_accurate_framebuffers =
use_accurate_gpu_emulation =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.

View File

@@ -175,7 +175,7 @@ int main(int argc, char** argv) {
Core::System& system{Core::System::GetInstance()};
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
Service::FileSystem::CreateFactories(system.GetFilesystem());
Service::FileSystem::CreateFactories(*system.GetFilesystem());
SCOPE_EXIT({ system.Shutdown(); });