Compare commits

..

118 Commits

Author SHA1 Message Date
Zach Hilman
bdaa76c0db ns: Implement command 400: GetApplicationControlData
Returns the raw NACP bytes and the raw icon bytes into a title-provided buffer. Pulls from Registration Cache for control data, returning all zeros should it not exist.
2018-10-29 16:20:16 -04:00
Zach Hilman
5ee19add1b fsp_srv: Implement ISaveDataInfoReader
An object to read SaveDataInfo objects, which describe a unique save on the system. This implementation iterates through all the directories in the save data space and uses the paths to reconstruct the metadata.
2018-10-29 13:54:39 -04:00
Zach Hilman
2e8177f0c9 fsp_srv: Implement command 61: OpenSaveDataInfoReaderBySaveDataSpaceId
Needed by Checkpoint. Returns an object that can iterate through all savedata on the system.
2018-10-29 13:54:39 -04:00
Zach Hilman
df264d2ccb savedata_factory: Expose accessors for SaveDataSpace 2018-10-29 13:54:38 -04:00
Zach Hilman
f2f679bf3f loader/nro: Call RegisterRomFS from Load
Allows NRO homebrew to use the RomFS in the ASET section.
2018-10-29 13:54:38 -04:00
Zach Hilman
b04e39107f control_metadata: Add GetRawBytes function to NACP
Returns the raw bytes of the NACP file. Needed for GetApplicationControlData which returns the raw, unprocessed NACP to the game.
2018-10-29 13:54:38 -04:00
bunnei
5d7167dfca Merge pull request #1610 from slashiee/dxt1-alpha
renderer_opengl: Enable alpha channel for DXT1 texture format
2018-10-28 21:29:43 -04:00
bunnei
9049aedd83 Merge pull request #1612 from Tinob/master
renderer_opengl: Correct bpp value for ASTC_2D_8X5_SRGB
2018-10-28 21:28:34 -04:00
Rodolfo Bogado
e8b565b239 renderer_opengl: Correct bpp value for ASTC_2D_8X5_SRGB 2018-10-28 20:52:57 -03:00
Michael
635d1e5651 Enable alpha channel for DXT1 texture format 2018-10-28 14:11:04 -07:00
bunnei
b5f8a5f0a3 Merge pull request #1607 from FearlessTobi/patch-3
renderer_opengl: Correct bpp value for ASTC_2D_8X5
2018-10-28 16:59:13 -04:00
Tobias
351d5a2227 Correct bpp value for ASTC_2D_8X5 2018-10-28 19:49:10 +01:00
bunnei
aa1cf608ed Merge pull request #1601 from FernandoS27/shader-precision
Improved Shader accuracy on Vertex and Geometry Shaders.
2018-10-28 13:06:21 -04:00
bunnei
4ddbd9bbaf Merge pull request #1606 from FearlessTobi/revert-1581-macosx-target-version
Revert "Update MACOSX_DEPLOYMENT_TARGET to 10.14"
2018-10-28 12:11:00 -04:00
FernandoS27
e5ca097e32 Refactor precise usage and add FMNMX, MUFU, FMUL32 and FADD332 2018-10-28 11:38:40 -04:00
Tobias
03150a560e Revert "Update MACOSX_DEPLOYMENT_TARGET to 10.14" 2018-10-28 13:43:42 +01:00
bunnei
2239d47112 Merge pull request #1593 from lioncash/svc
svc: Implement svcGetInfo command 0xF0000002
2018-10-28 04:38:48 -04:00
bunnei
b32be35173 Merge pull request #1581 from FreddyFunk/macosx-target-version
Update MACOSX_DEPLOYMENT_TARGET to 10.14
2018-10-28 04:32:11 -04:00
Frederic L
72d10ce66c file_sys/patch_manager: Remove unnecessary if-statements (#1586)
* remove unnecessary if-statements

* Addressed feedback
2018-10-28 02:30:29 -04:00
bunnei
0d449b77e2 Merge pull request #1598 from DeeJayBro/delete-directory
service/filesystem: Implemented DeleteDirectory & DeleteDirectoryRecursive
2018-10-28 02:29:52 -04:00
bunnei
6f620b2441 Merge pull request #1600 from DarkLordZach/nsp-secondary-loader-fix
loader/nsp: Move secondary loader initialization to constructor
2018-10-28 02:29:14 -04:00
bunnei
1fca683388 Merge pull request #1582 from Tinob/master
Implement sRGB support
2018-10-28 00:24:36 -04:00
Rodolfo Bogado
0287b2be6d Implement sRGB Support, including workarounds for nvidia driver issues and QT sRGB support 2018-10-28 01:13:55 -03:00
bunnei
a9d60c6103 Merge pull request #1602 from DarkLordZach/key-derivation-isxdigit
key_manager: Use isxdigit instead of isdigit when reading key file
2018-10-27 23:54:48 -04:00
Zach Hilman
1fa31cf74d key_manager: Use isxdigit instead of isdigit when reading key file
Crypto revisions are hex numbers and this function only checks if the string is valid for stoul in base 16, so it should be isxdigit.
2018-10-27 21:56:10 -04:00
bunnei
8d89e88d2c Merge pull request #1597 from lioncash/error
configure_system: Indicate when filesystem operations fail
2018-10-27 21:09:31 -04:00
bunnei
d63f5acb15 Merge pull request #1594 from FreddyFunk/static-cast
gl_rasterizer_cache: Fix compiler warning
2018-10-27 21:09:06 -04:00
FernandoS27
d8d557df86 Improved Shader accuracy on Vertex and Geometry Shaders with FFMA, FMUL and FADD 2018-10-27 20:09:26 -04:00
bunnei
a9dc34ea5c Merge pull request #1596 from FearlessTobi/port-4367
Port citra-emu/citra#4367: "cubeb_sink: ignore null-name device when selecting"
2018-10-27 13:26:24 -04:00
bunnei
ed95ce6bb7 Merge pull request #1592 from bunnei/prim-restart
gl_rasterizer: Implement primitive restart.
2018-10-27 13:25:00 -04:00
bunnei
ac8231ed10 Merge pull request #1599 from FernandoS27/stalemate
Implement Default Block Height for each format
2018-10-27 12:05:14 -04:00
FernandoS27
705300992e Implement Default Block Height for each format 2018-10-27 10:17:39 -04:00
Zach Hilman
7c70746ec4 loader/nsp: Move secondary loader initialization to constructor
Prevents nullptr bug when trying to dump the RomFS of an NSP resulting from secondary_loader not being initialized.
2018-10-27 10:16:29 -04:00
Frederic Laing
0bf24d310e gl_rasterizer_cache: Fix compiler warning 2018-10-27 13:06:26 +02:00
DeeJayBro
3b1e4c0995 service/filesystem: Add DirectoryDelete & DirectoryDeleteRecursively 2018-10-27 11:56:39 +02:00
Lioncash
9024cbb5b8 configure_system: Make GetIcon() return the scaled 64x64 icon
Avoids the need to put the scaling parameters all over the place for the
common case. The only other time scaling is done is to generate the
smaller 48x48 image, so this is fine.
2018-10-27 01:05:56 -04:00
Lioncash
85ed0af84e configure_system: Move entry formatting for the user account list entries to its own function
Avoids the need to duplicate this all over the place, and makes it
translator-friendly across the board.
2018-10-27 01:05:56 -04:00
Lioncash
8eaf857d06 configure_system: Display errors to the user if file operations fail when setting user images
We should display an error to the user if setting a user image for an
account fails, rather than continuing onwards.
2018-10-27 01:05:50 -04:00
Weiyi Wang
12c365b549 cubeb_sink: ignore null-name device when selecting
We already ignore them on listing devices. We should do the same when selecting devices. This fix a crash when opening a specific device while there is a null device in the list
2018-10-27 00:43:04 +02:00
Lioncash
7de8e36343 svc: Localize the GetInfo enum class to the function itself
Nothing from this enum is intended to be used outside of this function.
2018-10-26 12:49:14 -04:00
Lioncash
6594853eb1 svc: Implement svcGetInfo command 0xF0000002
This retrieves:

if (curr_thread == handle_thread) {
   result = total_thread_ticks + (hardware_tick_count - last_context_switch_ticks);
} else if (curr_thread == handle_thread && sub_id == current_core_index) {
   result = hardware_tick_count - last_context_switch_ticks;
}
2018-10-26 12:49:11 -04:00
bunnei
58444a0376 gl_rasterizer: Implement primitive restart. 2018-10-26 00:42:57 -04:00
bunnei
d278f25bda Merge pull request #1533 from FernandoS27/lmem
Implemented Shader Local Memory
2018-10-26 00:16:25 -04:00
bunnei
72e6b31a07 Merge pull request #1430 from DarkLordZach/remove-promote-dir
vfs: Remove InterpretAsDirectory and related functions
2018-10-26 00:15:34 -04:00
bunnei
1f98dc30ea Merge pull request #1591 from bunnei/depth-range
gl_rasterizer: Implement depth range.
2018-10-26 00:12:20 -04:00
bunnei
949d9a7136 maxwell_3d: Add code for initializing register defaults. 2018-10-25 23:42:39 -04:00
bunnei
debabf1fa6 Merge pull request #1569 from lioncash/amiibo
yuzu/main: Notify user of loading errors with Amiibo data
2018-10-25 22:10:08 -04:00
bunnei
476b9f8fc5 Merge pull request #1587 from lioncash/private
configure_system: Minor cleanup-related changes
2018-10-25 22:08:06 -04:00
bunnei
a94831f2a9 Merge pull request #1557 from bunnei/ldr_ro
Implement the LoadNro functions from the ldr:ro service.
2018-10-25 21:54:35 -04:00
bunnei
8cea598158 gl_rasterizer: Implement depth range. 2018-10-25 21:53:24 -04:00
bunnei
cebce2a93a ldr: Partially implement LoadNro.
- This is an incomplete implementation. It was tested with Super Mario Party.
2018-10-25 18:03:54 -04:00
bunnei
c2049aa4e5 process: LoadModule should clear JIT instruction cache. 2018-10-25 18:03:54 -04:00
bunnei
a609b6907a Kernel/Memory: Added a function to first a suitable guest address at which to allocate a region of a given size. 2018-10-25 18:03:54 -04:00
bunnei
ef7b2237d9 nro: Make LoadNro method accessible outside of apploader code. 2018-10-25 18:03:54 -04:00
Mat M
a141b46b5c Merge pull request #1583 from DarkLordZach/rle-size
ips_layer: Use rle_size instead of data_size in RLE patch application
2018-10-25 17:54:38 -04:00
Lioncash
85285b09b0 configure_system: Make the file selector text translatable
This should be localizable, since it's user-facing text.
2018-10-25 17:27:30 -04:00
Lioncash
5172354e29 configure_system: Make GetAccountUsername() an internal function
We can just make the function accept an arbitrary ProfileManager
reference and operate on that instead of tying the function to the class
itself. This allows us to keep the function internal to the cpp file and
removes the need to forward declare the UUID struct.
2018-10-25 17:27:25 -04:00
Lioncash
bf7da804c5 configure_system: Default initialize member variables
These should be initialized to deterministic values so it's easier to
catch improper behavior, as it'll always be reproducable, instead of
performing uninitialized reads.
2018-10-25 16:52:23 -04:00
Lioncash
8806e69f59 configure_system: Simplify UUID generation call in AddUser()
This is a static function so we can just perform an assignment directly.
2018-10-25 16:50:07 -04:00
Lioncash
a6addb5332 configure_system: Amend function casing 2018-10-25 16:47:09 -04:00
Lioncash
2347e1b8c5 configure_system: Add missing override specifier on the destructor 2018-10-25 16:45:13 -04:00
Lioncash
3c63cecb96 configure_system: Make public slots private
These are only used within this class, so we can make them private to
keep their use contained. This also gets rid of the pre-Qt5 'slot'
identifier, since Qt 5's connection syntax doesn't require a function to
be declared a slot anymore.
2018-10-25 16:43:44 -04:00
James Rowe
e54c9e19f3 Merge pull request #1584 from FearlessTobi/patch-3
Delete git file
2018-10-25 13:55:10 -06:00
Tobias
bf0d3e1fea Delete git 2018-10-25 21:53:41 +02:00
Zach Hilman
9a87ece837 ips_layer: Use rle_size instead of data_size in RLE patch application
Prevents a potential bug when using RLE records in an IPS patch.
2018-10-25 14:23:56 -04:00
bunnei
2bc2b32662 Merge pull request #1579 from lioncash/usb
service/usb: Update service function tables
2018-10-25 12:22:57 -04:00
bunnei
0634aee267 Merge pull request #1576 from lioncash/acc-warn
service/acc: Silence compiler truncation warnings
2018-10-25 12:22:10 -04:00
Frederic L
942def7831 Update MACOSX_DEPLOYMENT_TARGET to 10.14 2018-10-25 08:35:42 +02:00
bunnei
c94db0e071 Merge pull request #1577 from lioncash/err
kernel/error: Amend error return code values
2018-10-25 01:10:26 -04:00
bunnei
f7a173de6c Merge pull request #1524 from FernandoS27/layers-fix
rasterizer: Fix Layered Textures Loading and Cubemaps
2018-10-25 00:29:18 -04:00
bunnei
b43cfe6c02 Merge pull request #1575 from lioncash/qstring
game_list_worker: Use QString's formatting instead of fmt in FormatPatchNameVersions()
2018-10-24 19:39:43 -04:00
Lioncash
fbbb58b226 service/usb: Update service function tables
Updated based off the information provided by Hexkyz on Switchbrew.
2018-10-24 19:07:55 -04:00
bunnei
9aa5c1894e Merge pull request #1570 from lioncash/optional
profile_manager: Use std::optional instead of boost::optional
2018-10-24 18:11:03 -04:00
bunnei
3a6e76e9b5 Merge pull request #1558 from lioncash/ptr
yuzu/configuration/config: Use a std::unique_ptr for qt_config instead of a raw pointer
2018-10-24 18:07:14 -04:00
FernandoS27
ca142f35c0 Implemented LD_L and ST_L 2018-10-24 17:51:53 -04:00
FernandoS27
abefe29398 Implement Shader Local Memory 2018-10-24 17:50:43 -04:00
bunnei
29f748a658 Merge pull request #1565 from lioncash/audio
time_stretch: Remove unused m_channel_count member variable
2018-10-24 17:39:53 -04:00
bunnei
69b35d7615 Merge pull request #1554 from FernandoS27/pointsize
Implement PointSize Output Attribute.
2018-10-24 17:38:38 -04:00
bunnei
b723390ab1 Merge pull request #1571 from lioncash/debug-translate
graphic_breakpoints: Correct translation of strings in BreakpointModel's data() function
2018-10-24 17:37:18 -04:00
bunnei
ce2403d975 Merge pull request #1564 from lioncash/npad
npad: Remove unused controller variable from OnInit()
2018-10-24 17:36:55 -04:00
bunnei
d9590d7dfa Merge pull request #1568 from lioncash/dir
game_list: Use QFileInfo instead of common's file functions
2018-10-24 17:13:51 -04:00
bunnei
2694b43d3a Merge pull request #1567 from lioncash/translate
game_list: Make game list column headers translatable
2018-10-24 17:13:08 -04:00
bunnei
e6e17a3fc6 Merge pull request #1566 from lioncash/str
bootmanager: Use QStringLiteral instead of std::string to represent the window title.
2018-10-24 17:12:53 -04:00
bunnei
ddff188c65 Merge pull request #1563 from lioncash/frame
perf_stats: Remove unused variable within DoFrameLimiting()
2018-10-24 16:29:16 -04:00
bunnei
d14ba122e2 Merge pull request #1562 from lioncash/aoc
aoc_u: Make use of previously-unused CheckAOCTitleIDMatchesBase() function
2018-10-24 16:28:56 -04:00
bunnei
2eff8336f4 Merge pull request #1560 from lioncash/unused
maxwell_3d/decoders: Remove unused variables
2018-10-24 16:28:38 -04:00
bunnei
cdd499c261 Merge pull request #1561 from lioncash/fs
file_sys: Remove unused variables
2018-10-24 16:28:17 -04:00
bunnei
e65f5e4d66 Merge pull request #1559 from lioncash/log
logging/backend: Add missing services to the log filters
2018-10-24 16:28:01 -04:00
Lioncash
1fb4bebb63 kernel/errors: Remove now-unused, unnecessary, error codes
Now that we've gotten the innaccurate error codes out of the way, we can
finally toss away a bunch of these, trimming down the error codes to
ones that are actually used and knocking out two TODO comments.
2018-10-24 14:58:37 -04:00
Lioncash
239dfea34a kernel/shared_memory: Return ERR_INVALID_MEMORY_PERMISSIONS instead of ERR_INVALID_COMBINATION
This is more consistent with what the kernel does.
2018-10-24 14:54:36 -04:00
Lioncash
474bc29208 kernel/server_port: Simplify emptiness check within ShouldWait() 2018-10-24 14:24:36 -04:00
Lioncash
b703aba622 kernel/server_port: Change error case return value in Accept() to ERR_NOT_FOUND
This is what the kernel does in this instance.
2018-10-24 14:23:38 -04:00
Lioncash
afb7e5cc05 kernel/error: Remove leftover 3DS error codes
These are now entirely unused and can be removed.
2018-10-24 14:21:37 -04:00
Lioncash
fcf8f53a63 kernel/svc: Amend returned error code for invalid priorities in CreateThread
Like with the previous change, the kernel doesn't return NOT_AUTHORIZED
here. It returns INVALID_THREAD_PRIORITY.
2018-10-24 14:11:11 -04:00
Lioncash
77328b0f19 kernel/svc: Move and correct returned error code for invalid thread priorities in SetThreadPriority()
All priority checks are supposed to occur before checking the validity
of the thread handle, we're also not supposed to return
ERR_NOT_AUTHORIZED here.
2018-10-24 14:10:48 -04:00
Lioncash
c7c346a15d kernel/error: Add error code for invalid pointers
The kernel appears to return 0xE601 for this situation. Particularly in
svcWaitSynchronization, svcReplyAndReceive, and svcGetThreadContext
2018-10-24 13:41:32 -04:00
Lioncash
6df09f5b76 kernel/error: Add error code for closed sessions
The kernel appears to return 0xF601 for this case.
2018-10-24 13:38:39 -04:00
Lioncash
1edf8660bc game_list_worker: Use QString's formatting instead of fmt in FormatPatchNameVersions()
Using fmt here requires unnecessary string conversions back into
QString. Instead, we can just use QString's formatting and get the end
result of the formatting operation in the proper type.
2018-10-24 11:27:35 -04:00
Lioncash
4a31f99a02 profile_manager: Use std::optional instead of boost::optional
Now that we can actually use std::optional on macOS, we don't need to
continue using boost::optional here.
2018-10-24 11:06:52 -04:00
Lioncash
030847d5fa graphic_breakpoints: Correct translation of strings in BreakpointModel's data() function
tr() will not function properly on static/global data like this, as the
object is only ever constructed once, so the strings won't translate if
the language is changed without restarting the program, which is
undesirable. Instead we can just turn the map into a plain old function
that maps the values to their equivalent strings. This is also lessens
the memory allocated, since it's only allocating memory for the strings
themselves, and not an encompassing map as well.
2018-10-24 11:01:23 -04:00
Lioncash
bed2d6c425 yuzu/main: Notify user of loading errors with Amiibo data
We shouldn't silently continue if loading failed, since the general
assumption is that no messages showing up implicitly indicates success.
2018-10-24 10:39:31 -04:00
Lioncash
a1c85b8c55 game_list: Use QFileInfo instead of common's file functions
We can just use the facilities that Qt provides instead of pulling in
stuff from common. While we're at it, we can also simplify the nearby
logging statement's argument by just calling .toStdString()
2018-10-24 08:40:22 -04:00
Lioncash
47f081d513 game_list: Make game list column headers translatable
These are user-facing strings, so they should be marked as translatable
2018-10-24 08:20:35 -04:00
Lioncash
2cbc284c2b bootmanager: Use QStringLiteral instead of std::string to represent the window title
This gets rid of an unnecessary type conversion. We can just use the
regular QStringLiteral to already format the string as the type
setWindowTitle accepts instead of converting from a std::string
instance.
2018-10-24 08:14:26 -04:00
Lioncash
6d27614994 time_stretch: Remove unused m_channel_count member variable
This is only stored to, but never read from.
2018-10-24 00:46:17 -04:00
Lioncash
93596d03ec npad: Remove unused controller variable from OnInit()
This also gets rid of variable shadowing related to the lambda parameter
a little bit below this code as well.
2018-10-24 00:38:03 -04:00
Lioncash
7c9f7aeacc perf_stats: Remove unused variable within DoFrameLimiting()
This hasn't been used since ba8ff096fd
2018-10-24 00:33:26 -04:00
Lioncash
6949f73149 yuzu/configuration/config: Use a std::unique_ptr for qt_config instead of a raw pointer
Same behavior, less code.
2018-10-24 00:24:02 -04:00
Lioncash
a3d1ede25f aoc_u: Make use of previously-unused CheckAOCTitleIDMatchesBase() function
We can just call the function instead of duplicating the code here. This
also prevents an unused function warning.

We also don't need to take the lambda capture by reference. It's just a
u64 value, so by value is fine here.
2018-10-24 00:13:08 -04:00
Lioncash
c7c594a6b8 vfs: Handle failure of file reading within VfsRawCopy()
Also gets rid of an unused variable.
2018-10-24 00:01:32 -04:00
Lioncash
c6529688fc key_manager: Remove unused variable in DeriveBase() 2018-10-24 00:00:12 -04:00
Lioncash
257b7bbfee decoders: Remove unused variable within SwizzledData() 2018-10-23 23:51:13 -04:00
Lioncash
a97cdb5eb4 maxwell_3d: Remove unused variable within ProcessQueryGet() 2018-10-23 23:50:16 -04:00
Lioncash
f80b80b922 logging/backend: Add missing services to the log filters
Just a few overlooked services.
2018-10-23 22:35:59 -04:00
Lioncash
6f5bede402 yuzu/configuration/config: Reorganize member variable and function layout
Makes the class layout consistent with the others.
2018-10-23 21:46:49 -04:00
FernandoS27
ed8ca608a0 Implement PointSize 2018-10-23 15:08:00 -04:00
FernandoS27
e0ea2f5f6e Fixed Layered Textures Loading and Cubemaps 2018-10-23 14:27:36 -04:00
Zach Hilman
a25d79cfaa vfs: Remove InterpretAsDirectory and related functions
When writing VFS, it initally seemed useful to include a function to in-place convert container files into directories in one homogenous directory structure, but re-evaluating it now there have been plenty of chances to use it and there has always been a better way. Removing as it is unused and likely will not be used.
2018-10-19 14:02:07 -04:00
87 changed files with 1506 additions and 607 deletions

0
git
View File

View File

@@ -121,7 +121,8 @@ CubebSink::CubebSink(std::string target_device_name) {
const auto collection_end{collection.device + collection.count};
const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
return target_device_name == info.friendly_name;
return info.friendly_name != nullptr &&
target_device_name == info.friendly_name;
})};
if (device != collection_end) {
output_device = device->devid;

View File

@@ -10,8 +10,7 @@
namespace AudioCore {
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
: m_sample_rate(sample_rate), m_channel_count(channel_count) {
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
m_sound_touch.setChannels(channel_count);
m_sound_touch.setSampleRate(sample_rate);
m_sound_touch.setPitch(1.0);

View File

@@ -27,7 +27,6 @@ public:
private:
u32 m_sample_rate;
u32 m_channel_count;
soundtouch::SoundTouch m_sound_touch;
double m_stretch_ratio = 1.0;
};

View File

@@ -196,6 +196,7 @@ void FileBackend::Write(const Entry& entry) {
SUB(Service, NFP) \
SUB(Service, NIFM) \
SUB(Service, NIM) \
SUB(Service, NPNS) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
SUB(Service, PCIE) \
@@ -204,10 +205,12 @@ void FileBackend::Write(const Entry& entry) {
SUB(Service, PM) \
SUB(Service, PREPO) \
SUB(Service, PSC) \
SUB(Service, PSM) \
SUB(Service, SET) \
SUB(Service, SM) \
SUB(Service, SPL) \
SUB(Service, SSL) \
SUB(Service, TCAP) \
SUB(Service, Time) \
SUB(Service, USB) \
SUB(Service, VI) \

View File

@@ -83,6 +83,7 @@ enum class Class : ClassType {
Service_NFP, ///< The NFP service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service
Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
Service_PCIE, ///< The PCIe service
@@ -96,6 +97,7 @@ enum class Class : ClassType {
Service_SM, ///< The SM (Service manager) service
Service_SPL, ///< The SPL service
Service_SSL, ///< The SSL service
Service_TCAP, ///< The TCAP service.
Service_Time, ///< The time service
Service_USB, ///< The USB (Universal Serial Bus) service
Service_VI, ///< The VI (Video interface) service

View File

@@ -395,7 +395,7 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
if (base.size() < begin + length)
return false;
return std::all_of(base.begin() + begin, base.begin() + begin + length,
[](u8 c) { return std::isdigit(c); });
[](u8 c) { return std::isxdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
@@ -713,7 +713,6 @@ void KeyManager::DeriveBase() {
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])

View File

@@ -168,10 +168,6 @@ VirtualDir XCI::GetParentDirectory() const {
return file->GetContainingDirectory();
}
bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
if (partitions[static_cast<std::size_t>(part)] == nullptr) {
return Loader::ResultStatus::ErrorXCIMissingPartition;

View File

@@ -94,9 +94,6 @@ public:
VirtualDir GetParentDirectory() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);

View File

@@ -546,7 +546,4 @@ u64 NCA::GetBaseIVFCOffset() const {
return ivfc_offset;
}
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -100,9 +100,6 @@ public:
// Returns the base ivfc offset used in BKTR patching.
u64 GetBaseIVFCOffset() const;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
bool CheckSupportedNCA(const NCAHeader& header);
bool HandlePotentialHeaderDecryption();

View File

@@ -66,4 +66,10 @@ std::string NACP::GetVersionString() const {
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
raw->version_string.size());
}
std::vector<u8> NACP::GetRawBytes() const {
std::vector<u8> out(sizeof(RawNACP));
std::memcpy(out.data(), raw.get(), sizeof(RawNACP));
return out;
}
} // namespace FileSys

View File

@@ -81,6 +81,7 @@ public:
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
std::vector<u8> GetRawBytes() const;
private:
std::unique_ptr<RawNACP> raw;

View File

@@ -99,7 +99,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
u16 rle_size{};
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
return nullptr;
rle_size = Common::swap16(data_size);
rle_size = Common::swap16(rle_size);
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);

View File

@@ -83,7 +83,7 @@ std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
}
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
return pfs_dirs;
return {};
}
std::string PartitionFilesystem::GetName() const {
@@ -103,18 +103,4 @@ void PartitionFilesystem::PrintDebugInfo() const {
pfs_files[i]->GetName(), pfs_files[i]->GetSize());
}
}
bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
const auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
if (iter == pfs_files.end())
return false;
const std::ptrdiff_t offset = std::distance(pfs_files.begin(), iter);
pfs_files[offset] = std::move(pfs_files.back());
pfs_files.pop_back();
pfs_dirs.emplace_back(std::move(dir));
return true;
}
} // namespace FileSys

View File

@@ -35,9 +35,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
void PrintDebugInfo() const;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
struct Header {
u32_le magic;
@@ -84,7 +81,6 @@ private:
std::size_t content_offset = 0;
std::vector<VirtualFile> pfs_files;
std::vector<VirtualDir> pfs_dirs;
};
} // namespace FileSys

View File

@@ -61,13 +61,12 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
if (update != nullptr) {
if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
update->GetExeFS() != nullptr) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
exefs = update->GetExeFS();
}
if (update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
exefs = update->GetExeFS();
}
return exefs;

View File

@@ -83,6 +83,24 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
return MakeResult<VirtualDir>(std::move(out));
}
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
}
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
switch (space) {
case SaveDataSpaceId::NandSystem:
return "/system/";
case SaveDataSpaceId::NandUser:
return "/user/";
case SaveDataSpaceId::TemporaryStorage:
return "/temp/";
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
}
}
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
@@ -90,21 +108,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
if (type == SaveDataType::SaveData && title_id == 0)
title_id = Core::CurrentProcess()->GetTitleID();
std::string out;
switch (space) {
case SaveDataSpaceId::NandSystem:
out = "/system/";
break;
case SaveDataSpaceId::NandUser:
out = "/user/";
break;
case SaveDataSpaceId::TemporaryStorage:
out = "/temp/";
break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
}
std::string out = GetSaveDataSpaceIdPath(space);
switch (type) {
case SaveDataType::SystemSaveData:

View File

@@ -52,6 +52,9 @@ public:
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id);

View File

@@ -205,10 +205,6 @@ VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;

View File

@@ -55,9 +55,6 @@ public:
VirtualDir GetParentDirectory() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);

View File

@@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo
std::vector<u8> temp(std::min(block_size, src->GetSize()));
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
const auto read = std::min(block_size, src->GetSize() - i);
const auto block = src->Read(temp.data(), read, i);
if (dest->Write(temp.data(), read, i) != read)
if (src->Read(temp.data(), read, i) != read) {
return false;
}
if (dest->Write(temp.data(), read, i) != read) {
return false;
}
}
return true;

View File

@@ -262,36 +262,8 @@ public:
// item name -> type.
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
// Interprets the file with name file instead as a directory of type directory.
// The directory must have a constructor that takes a single argument of type
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
// subdirectory in one call.
template <typename Directory>
bool InterpretAsDirectory(std::string_view file) {
auto file_p = GetFile(file);
if (file_p == nullptr) {
return false;
}
return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p));
}
bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function,
const std::string& file) {
auto file_p = GetFile(file);
if (file_p == nullptr)
return false;
return ReplaceFileWithSubdirectory(file_p, function(file_p));
}
// Returns the full path of this directory as a string, recursively
virtual std::string GetFullPath() const;
protected:
// Backend for InterpretAsDirectory.
// Removes all references to file and adds a reference to dir in the directory's implementation.
virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
};
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work

View File

@@ -126,7 +126,4 @@ bool LayeredVfsDirectory::Rename(std::string_view name_) {
return true;
}
bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -39,9 +39,6 @@ public:
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::vector<VirtualDir> dirs;
std::string name;

View File

@@ -430,7 +430,4 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
return out;
}
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -100,9 +100,6 @@ public:
std::string GetFullPath() const override;
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);

View File

@@ -132,11 +132,4 @@ void VectorVfsDirectory::AddFile(VirtualFile file) {
void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
dirs.push_back(std::move(dir));
}
bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
if (!DeleteFile(file->GetName()))
return false;
dirs.emplace_back(std::move(dir));
return true;
}
} // namespace FileSys

View File

@@ -57,9 +57,6 @@ public:
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::vector<VirtualFile> files;
std::vector<VirtualDir> dirs;

View File

@@ -163,7 +163,4 @@ std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
return file->GetContainingDirectory();
}
bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -51,9 +51,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
Loader::ResultStatus Parse(std::string_view path);

View File

@@ -14,11 +14,6 @@ namespace IPC {
/// Size of the command buffer area, in 32-bit words.
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
// These errors are commonly returned by invalid IPC translations, so alias them here for
// convenience.
// TODO(yuriks): These will probably go away once translation is implemented inside the kernel.
constexpr auto ERR_INVALID_HANDLE = Kernel::ERR_INVALID_HANDLE_OS;
enum class ControlCommand : u32 {
ConvertSessionToDomain = 0,
ConvertDomainToSession = 1,

View File

@@ -10,11 +10,6 @@ namespace Kernel {
namespace ErrCodes {
enum {
// TODO(Subv): Remove these 3DS OS error codes.
SessionClosedByRemote = 26,
NoPendingSessions = 35,
InvalidBufferDescriptor = 48,
// Confirmed Switch OS error codes
MaxConnectionsReached = 7,
InvalidSize = 101,
@@ -26,6 +21,7 @@ enum {
InvalidThreadPriority = 112,
InvalidProcessorId = 113,
InvalidHandle = 114,
InvalidPointer = 115,
InvalidCombination = 116,
Timeout = 117,
SynchronizationCanceled = 118,
@@ -33,6 +29,7 @@ enum {
InvalidEnumValue = 120,
NoSuchEntry = 121,
AlreadyRegistered = 122,
SessionClosed = 123,
InvalidState = 125,
ResourceLimitExceeded = 132,
};
@@ -41,18 +38,14 @@ enum {
// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always
// double check that the code matches before re-using the constant.
// TODO(bunnei): Replace -1 with correct errors for Switch OS
constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull);
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(ErrorModule::Kernel, ErrCodes::SessionClosed);
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge);
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrorModule::Kernel,
ErrCodes::MaxConnectionsReached);
constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
constexpr ResultCode ERR_INVALID_COMBINATION(-1);
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorModule::Kernel,
ErrCodes::InvalidCombination);
constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
@@ -65,14 +58,8 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::Alrea
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
ErrCodes::InvalidThreadPriority);
constexpr ResultCode ERR_INVALID_POINTER(-1);
constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
/// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths.
constexpr ResultCode ERR_INVALID_HANDLE_OS(-1);
constexpr ResultCode ERR_INVALID_POINTER(ErrorModule::Kernel, ErrCodes::InvalidPointer);
constexpr ResultCode ERR_NOT_FOUND(ErrorModule::Kernel, ErrCodes::NoSuchEntry);
constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout);
/// Returned when Accept() is called on a port with no sessions to be accepted.
constexpr ResultCode ERR_NO_PENDING_SESSIONS(-1);
} // namespace Kernel

View File

@@ -232,6 +232,12 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
// Clear instruction cache in CPU JIT
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
}
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {

View File

@@ -202,6 +202,16 @@ public:
return is_64bit_process;
}
/// Gets the total running time of the process instance in ticks.
u64 GetCPUTimeTicks() const {
return total_process_running_time_ticks;
}
/// Updates the total running time, adding the given ticks to it.
void UpdateCPUTimeTicks(u64 ticks) {
total_process_running_time_ticks += ticks;
}
/**
* Loads process-specifics configuration info with metadata provided
* by an executable.
@@ -305,6 +315,9 @@ private:
/// specified by metadata provided to the process during loading.
bool is_64bit_process = true;
/// Total running time for the process in ticks.
u64 total_process_running_time_ticks = 0;
/// Per-process handle table for storing created object handles in.
HandleTable handle_table;

View File

@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
@@ -34,6 +35,10 @@ Thread* Scheduler::GetCurrentThread() const {
return current_thread.get();
}
u64 Scheduler::GetLastContextSwitchTicks() const {
return last_context_switch_time;
}
Thread* Scheduler::PopNextReadyThread() {
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
@@ -54,7 +59,10 @@ Thread* Scheduler::PopNextReadyThread() {
}
void Scheduler::SwitchContext(Thread* new_thread) {
Thread* previous_thread = GetCurrentThread();
Thread* const previous_thread = GetCurrentThread();
Process* const previous_process = Core::CurrentProcess();
UpdateLastContextSwitchTime(previous_thread, previous_process);
// Save context for previous thread
if (previous_thread) {
@@ -78,8 +86,6 @@ void Scheduler::SwitchContext(Thread* new_thread) {
// Cancel any outstanding wakeup events for this thread
new_thread->CancelWakeupTimer();
auto* const previous_process = Core::CurrentProcess();
current_thread = new_thread;
ready_queue.remove(new_thread->GetPriority(), new_thread);
@@ -102,6 +108,22 @@ void Scheduler::SwitchContext(Thread* new_thread) {
}
}
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time;
const u64 most_recent_switch_ticks = CoreTiming::GetTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) {
thread->UpdateCPUTimeTicks(update_ticks);
}
if (process != nullptr) {
process->UpdateCPUTimeTicks(update_ticks);
}
last_context_switch_time = most_recent_switch_ticks;
}
void Scheduler::Reschedule() {
std::lock_guard<std::mutex> lock(scheduler_mutex);

View File

@@ -17,6 +17,8 @@ class ARM_Interface;
namespace Kernel {
class Process;
class Scheduler final {
public:
explicit Scheduler(Core::ARM_Interface& cpu_core);
@@ -31,6 +33,9 @@ public:
/// Gets the current running thread
Thread* GetCurrentThread() const;
/// Gets the timestamp for the last context switch in ticks.
u64 GetLastContextSwitchTicks() const;
/// Adds a new thread to the scheduler
void AddThread(SharedPtr<Thread> thread, u32 priority);
@@ -64,6 +69,19 @@ private:
*/
void SwitchContext(Thread* new_thread);
/**
* Called on every context switch to update the internal timestamp
* This also updates the running time ticks for the given thread and
* process using the following difference:
*
* ticks += most_recent_ticks - last_context_switch_ticks
*
* The internal tick timestamp for the scheduler is simply the
* most recent tick count retrieved. No special arithmetic is
* applied to it.
*/
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
/// Lists all thread ids that aren't deleted/etc.
std::vector<SharedPtr<Thread>> thread_list;
@@ -73,6 +91,7 @@ private:
SharedPtr<Thread> current_thread = nullptr;
Core::ARM_Interface& cpu_core;
u64 last_context_switch_time = 0;
static std::mutex scheduler_mutex;
};

View File

@@ -18,7 +18,7 @@ ServerPort::~ServerPort() = default;
ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
if (pending_sessions.empty()) {
return ERR_NO_PENDING_SESSIONS;
return ERR_NOT_FOUND;
}
auto session = std::move(pending_sessions.back());
@@ -28,7 +28,7 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
return pending_sessions.empty();
}
void ServerPort::Acquire(Thread* thread) {

View File

@@ -80,20 +80,19 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions) {
MemoryPermission own_other_permissions =
const MemoryPermission own_other_permissions =
target_process == owner_process ? this->permissions : this->other_permissions;
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
return ERR_INVALID_COMBINATION;
return ERR_INVALID_MEMORY_PERMISSIONS;
}
// Error out if the requested permissions don't match what the creator process allows.
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
GetObjectId(), address, name);
return ERR_INVALID_COMBINATION;
return ERR_INVALID_MEMORY_PERMISSIONS;
}
// Error out if the provided permissions are not compatible with what the creator process needs.

View File

@@ -467,6 +467,37 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
enum class GetInfoType : u64 {
// 1.0.0+
AllowedCpuIdBitmask = 0,
AllowedThreadPrioBitmask = 1,
MapRegionBaseAddr = 2,
MapRegionSize = 3,
HeapRegionBaseAddr = 4,
HeapRegionSize = 5,
TotalMemoryUsage = 6,
TotalHeapUsage = 7,
IsCurrentProcessBeingDebugged = 8,
ResourceHandleLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
// 2.0.0+
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
NewMapRegionBaseAddr = 14,
NewMapRegionSize = 15,
// 3.0.0+
IsVirtualAddressMemoryEnabled = 16,
PersonalMmHeapUsage = 17,
TitleId = 18,
// 4.0.0+
PrivilegedProcessId = 19,
// 5.0.0+
UserExceptionContextAddr = 20,
ThreadTickCount = 0xF0000002,
};
const auto* current_process = Core::CurrentProcess();
const auto& vm_manager = current_process->VMManager();
@@ -529,6 +560,36 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
"(STUBBED) Attempted to query user exception context address, returned 0");
*result = 0;
break;
case GetInfoType::ThreadTickCount: {
constexpr u64 num_cpus = 4;
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
return ERR_INVALID_COMBINATION_KERNEL;
}
const auto thread =
current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
if (!thread) {
return ERR_INVALID_HANDLE;
}
auto& system = Core::System::GetInstance();
const auto& scheduler = system.CurrentScheduler();
const auto* const current_thread = scheduler.GetCurrentThread();
const bool same_thread = current_thread == thread;
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
u64 out_ticks = 0;
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
}
*result = out_ticks;
break;
}
default:
UNIMPLEMENTED();
}
@@ -594,16 +655,17 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
}
const auto* const current_process = Core::CurrentProcess();
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
// Note: The kernel uses the current process's resource limit instead of
// the one from the thread owner's resource limit.
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_NOT_AUTHORIZED;
return ERR_INVALID_THREAD_PRIORITY;
}
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
thread->SetPriority(priority);
@@ -745,7 +807,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
auto* const current_process = Core::CurrentProcess();
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_NOT_AUTHORIZED;
return ERR_INVALID_THREAD_PRIORITY;
}
if (processor_id == THREADPROCESSORID_DEFAULT) {

View File

@@ -24,37 +24,6 @@ struct PageInfo {
u64 flags;
};
/// Values accepted by svcGetInfo
enum class GetInfoType : u64 {
// 1.0.0+
AllowedCpuIdBitmask = 0,
AllowedThreadPrioBitmask = 1,
MapRegionBaseAddr = 2,
MapRegionSize = 3,
HeapRegionBaseAddr = 4,
HeapRegionSize = 5,
TotalMemoryUsage = 6,
TotalHeapUsage = 7,
IsCurrentProcessBeingDebugged = 8,
ResourceHandleLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
// 2.0.0+
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
NewMapRegionBaseAddr = 14,
NewMapRegionSize = 15,
// 3.0.0+
IsVirtualAddressMemoryEnabled = 16,
PersonalMmHeapUsage = 17,
TitleId = 18,
// 4.0.0+
PrivilegedProcessId = 19,
// 5.0.0+
UserExceptionContextAddr = 20,
};
void CallSVC(u32 immediate);
} // namespace Kernel

View File

@@ -258,6 +258,14 @@ public:
return last_running_ticks;
}
u64 GetTotalCPUTimeTicks() const {
return total_cpu_time_ticks;
}
void UpdateCPUTimeTicks(u64 ticks) {
total_cpu_time_ticks += ticks;
}
s32 GetProcessorID() const {
return processor_id;
}
@@ -378,7 +386,8 @@ private:
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
s32 processor_id = 0;

View File

@@ -143,6 +143,26 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
}
ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
// Find the first Free VMA.
const VAddr base = GetASLRRegionBaseAddress();
const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
if (vma.second.type != VMAType::Free)
return false;
const VAddr vma_end = vma.second.base + vma.second.size;
return vma_end > base && vma_end >= base + size;
});
if (vma_handle == vma_map.end()) {
// TODO(Subv): Find the correct error code here.
return ResultCode(-1);
}
const VAddr target = std::max(base, vma_handle->second.base);
return MakeResult<VAddr>(target);
}
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
MemoryState state,
Memory::MemoryHookPointer mmio_handler) {

View File

@@ -157,6 +157,14 @@ public:
*/
ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
/**
* Finds the first free address that can hold a region of the desired size.
*
* @param size Size of the desired region.
* @return The found free address.
*/
ResultVal<VAddr> FindFreeRegion(u64 size) const;
/**
* Maps a memory-mapped IO region at a given address.
*

View File

@@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include <random>
#include <boost/optional.hpp>
#include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
@@ -58,11 +58,11 @@ ProfileManager::~ProfileManager() {
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
boost::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
if (user_count >= MAX_USERS) {
return boost::none;
return {};
}
profiles[user_count] = user;
profiles[user_count] = profile;
return user_count++;
}
@@ -81,7 +81,7 @@ bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
/// Helper function to register a user to the system
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
if (AddToProfiles(user) == boost::none) {
if (!AddToProfiles(user)) {
return ERROR_TOO_MANY_USERS;
}
return RESULT_SUCCESS;
@@ -126,37 +126,40 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
return CreateNewUser(uuid, username_output);
}
boost::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
if (index >= MAX_USERS)
return boost::none;
std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
if (index >= MAX_USERS) {
return {};
}
return profiles[index].user_uuid;
}
/// Returns a users profile index based on their user id.
boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
return boost::none;
return {};
}
auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
const auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
return boost::none;
return {};
}
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
}
/// Returns a users profile index based on their profile
boost::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
return GetUserIndex(user.user_uuid);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
ProfileBase& profile) const {
if (index == boost::none || index >= MAX_USERS) {
bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
if (!index || index >= MAX_USERS) {
return false;
}
const auto& prof_info = profiles[index.get()];
const auto& prof_info = profiles[*index];
profile.user_uuid = prof_info.user_uuid;
profile.username = prof_info.username;
profile.timestamp = prof_info.creation_time;
@@ -165,7 +168,7 @@ bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
auto idx = GetUserIndex(uuid);
const auto idx = GetUserIndex(uuid);
return GetProfileBase(idx, profile);
}
@@ -192,7 +195,7 @@ std::size_t ProfileManager::GetOpenUserCount() const {
/// Checks if a user id exists in our profile manager
bool ProfileManager::UserExists(UUID uuid) const {
return (GetUserIndex(uuid) != boost::none);
return GetUserIndex(uuid) != std::nullopt;
}
bool ProfileManager::UserExistsIndex(std::size_t index) const {
@@ -203,21 +206,23 @@ bool ProfileManager::UserExistsIndex(std::size_t index) const {
/// Opens a specific user
void ProfileManager::OpenUser(UUID uuid) {
auto idx = GetUserIndex(uuid);
if (idx == boost::none) {
const auto idx = GetUserIndex(uuid);
if (!idx) {
return;
}
profiles[idx.get()].is_open = true;
profiles[*idx].is_open = true;
last_opened_user = uuid;
}
/// Closes a specific user
void ProfileManager::CloseUser(UUID uuid) {
auto idx = GetUserIndex(uuid);
if (idx == boost::none) {
const auto idx = GetUserIndex(uuid);
if (!idx) {
return;
}
profiles[idx.get()].is_open = false;
profiles[*idx].is_open = false;
}
/// Gets all valid user ids on the system
@@ -247,10 +252,10 @@ UUID ProfileManager::GetLastOpenedUser() const {
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const {
if (GetProfileBase(index, profile)) {
data = profiles[index.get()].data;
data = profiles[*index].data;
return true;
}
return false;
@@ -259,7 +264,7 @@ bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, P
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
ProfileData& data) const {
auto idx = GetUserIndex(uuid);
const auto idx = GetUserIndex(uuid);
return GetProfileBaseAndData(idx, profile, data);
}
@@ -277,8 +282,8 @@ bool ProfileManager::CanSystemRegisterUser() const {
}
bool ProfileManager::RemoveUser(UUID uuid) {
auto index = GetUserIndex(uuid);
if (index == boost::none) {
const auto index = GetUserIndex(uuid);
if (!index) {
return false;
}
@@ -289,8 +294,8 @@ bool ProfileManager::RemoveUser(UUID uuid) {
}
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
auto index = GetUserIndex(uuid);
if (profile_new.user_uuid == UUID(INVALID_UUID) || index == boost::none) {
const auto index = GetUserIndex(uuid);
if (!index || profile_new.user_uuid == UUID(INVALID_UUID)) {
return false;
}

View File

@@ -5,8 +5,8 @@
#pragma once
#include <array>
#include <optional>
#include "boost/optional.hpp"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
@@ -96,13 +96,13 @@ public:
ResultCode AddUser(const ProfileInfo& user);
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
ResultCode CreateNewUser(UUID uuid, const std::string& username);
boost::optional<UUID> GetUser(std::size_t index) const;
boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const;
std::optional<UUID> GetUser(std::size_t index) const;
std::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
bool GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const;
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
@@ -120,16 +120,16 @@ public:
bool CanSystemRegisterUser() const;
bool RemoveUser(UUID uuid);
bool SetProfileBase(UUID uuid, const ProfileBase& profile);
bool SetProfileBase(UUID uuid, const ProfileBase& profile_new);
private:
void ParseUserSaveFile();
void WriteUserSaveFile();
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
std::array<ProfileInfo, MAX_USERS> profiles{};
std::size_t user_count = 0;
boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
UUID last_opened_user{INVALID_UUID};
};

View File

@@ -743,7 +743,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
Account::ProfileManager profile_manager{};
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
ASSERT(uuid != boost::none);
ASSERT(uuid != std::nullopt);
params.current_user = uuid->uuid;
IPC::ResponseBuilder rb{ctx, 2, 0, 1};

View File

@@ -24,8 +24,8 @@ namespace Service::AOC {
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
}
static std::vector<u64> AccumulateAOCTitleIDs() {
@@ -74,7 +74,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
rb.Push<u32>(static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[&current](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; })));
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {

View File

@@ -309,6 +309,16 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
return save_data_factory->Open(space, save_struct);
}
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) {
LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space));
if (save_data_factory == nullptr) {
return ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound);
}
return MakeResult(save_data_factory->GetSaveDataSpaceDirectory(space));
}
ResultVal<FileSys::VirtualDir> OpenSDMC() {
LOG_TRACE(Service_FS, "Opening SDMC");

View File

@@ -45,6 +45,7 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId stora
FileSys::ContentRecordType type);
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space);
ResultVal<FileSys::VirtualDir> OpenSDMC();
std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();

View File

@@ -11,6 +11,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/directory.h"
@@ -273,8 +274,8 @@ public:
{0, &IFileSystem::CreateFile, "CreateFile"},
{1, &IFileSystem::DeleteFile, "DeleteFile"},
{2, &IFileSystem::CreateDirectory, "CreateDirectory"},
{3, nullptr, "DeleteDirectory"},
{4, nullptr, "DeleteDirectoryRecursively"},
{3, &IFileSystem::DeleteDirectory, "DeleteDirectory"},
{4, &IFileSystem::DeleteDirectoryRecursively, "DeleteDirectoryRecursively"},
{5, &IFileSystem::RenameFile, "RenameFile"},
{6, nullptr, "RenameDirectory"},
{7, &IFileSystem::GetEntryType, "GetEntryType"},
@@ -329,6 +330,30 @@ public:
rb.Push(backend.CreateDirectory(name));
}
void DeleteDirectory(Kernel::HLERequestContext& ctx) {
const IPC::RequestParser rp{ctx};
const auto file_buffer = ctx.ReadBuffer();
std::string name = Common::StringFromBuffer(file_buffer);
LOG_DEBUG(Service_FS, "called directory {}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteDirectory(name));
}
void DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
const IPC::RequestParser rp{ctx};
const auto file_buffer = ctx.ReadBuffer();
std::string name = Common::StringFromBuffer(file_buffer);
LOG_DEBUG(Service_FS, "called directory {}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteDirectoryRecursively(name));
}
void RenameFile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -427,7 +452,147 @@ private:
VfsDirectoryServiceWrapper backend;
};
class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
public:
explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space)
: ServiceFramework("ISaveDataInfoReader") {
static const FunctionInfo functions[] = {
{0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
};
RegisterHandlers(functions);
FindAllSaves(space);
}
void ReadSaveDataInfo(Kernel::HLERequestContext& ctx) {
// Calculate how many entries we can fit in the output buffer
const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(SaveDataInfo);
// Cap at total number of entries.
const u64 actual_entries = std::min(count_entries, info.size() - next_entry_index);
// Determine data start and end
const auto* begin = reinterpret_cast<u8*>(info.data() + next_entry_index);
const auto* end = reinterpret_cast<u8*>(info.data() + next_entry_index + actual_entries);
const auto range_size = static_cast<std::size_t>(std::distance(begin, end));
next_entry_index += actual_entries;
// Write the data to memory
ctx.WriteBuffer(begin, range_size);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(actual_entries));
}
private:
static u64 stoull_be(std::string_view str) {
if (str.size() != 16)
return 0;
const auto bytes = Common::HexStringToArray<0x8>(str);
u64 out{};
std::memcpy(&out, bytes.data(), sizeof(u64));
return Common::swap64(out);
}
void FindAllSaves(FileSys::SaveDataSpaceId space) {
const auto save_root = OpenSaveDataSpace(space);
ASSERT(save_root.Succeeded());
for (const auto& type : (*save_root)->GetSubdirectories()) {
if (type->GetName() == "save") {
for (const auto& save_id : type->GetSubdirectories()) {
for (const auto& user_id : save_id->GetSubdirectories()) {
const auto save_id_numeric = stoull_be(save_id->GetName());
auto user_id_numeric = Common::HexStringToArray<0x10>(user_id->GetName());
std::reverse(user_id_numeric.begin(), user_id_numeric.end());
if (save_id_numeric != 0) {
// System Save Data
info.emplace_back(SaveDataInfo{
0,
space,
FileSys::SaveDataType::SystemSaveData,
{},
user_id_numeric,
save_id_numeric,
0,
user_id->GetSize(),
{},
});
continue;
}
for (const auto& title_id : user_id->GetSubdirectories()) {
const auto device =
std::all_of(user_id_numeric.begin(), user_id_numeric.end(),
[](u8 val) { return val == 0; });
info.emplace_back(SaveDataInfo{
0,
space,
device ? FileSys::SaveDataType::DeviceSaveData
: FileSys::SaveDataType::SaveData,
{},
user_id_numeric,
save_id_numeric,
stoull_be(title_id->GetName()),
title_id->GetSize(),
{},
});
}
}
}
} else if (space == FileSys::SaveDataSpaceId::TemporaryStorage) {
// Temporary Storage
for (const auto& user_id : type->GetSubdirectories()) {
for (const auto& title_id : user_id->GetSubdirectories()) {
if (!title_id->GetFiles().empty() ||
!title_id->GetSubdirectories().empty()) {
auto user_id_numeric =
Common::HexStringToArray<0x10>(user_id->GetName());
std::reverse(user_id_numeric.begin(), user_id_numeric.end());
info.emplace_back(SaveDataInfo{
0,
space,
FileSys::SaveDataType::TemporaryStorage,
{},
user_id_numeric,
stoull_be(type->GetName()),
stoull_be(title_id->GetName()),
title_id->GetSize(),
{},
});
}
}
}
}
}
}
struct SaveDataInfo {
u64_le save_id_unknown;
FileSys::SaveDataSpaceId space;
FileSys::SaveDataType type;
INSERT_PADDING_BYTES(0x6);
std::array<u8, 0x10> user_id;
u64_le save_id;
u64_le title_id;
u64_le save_image_size;
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
std::vector<SaveDataInfo> info;
u64 next_entry_index = 0;
};
FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "MountContent"},
{1, &FSP_SRV::Initialize, "Initialize"},
@@ -461,7 +626,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{58, nullptr, "ReadSaveDataFileSystemExtraData"},
{59, nullptr, "WriteSaveDataFileSystemExtraData"},
{60, nullptr, "OpenSaveDataInfoReader"},
{61, nullptr, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
{61, &FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
{62, nullptr, "OpenCacheStorageList"},
{64, nullptr, "OpenSaveDataInternalStorageFileSystem"},
{65, nullptr, "UpdateSaveDataMacForDebug"},
@@ -520,6 +685,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{1009, nullptr, "GetAndClearMemoryReportInfo"},
{1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
};
// clang-format on
RegisterHandlers(functions);
}
@@ -594,6 +760,15 @@ void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) {
MountSaveData(ctx);
}
void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto space = rp.PopRaw<FileSys::SaveDataSpaceId>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space));
}
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called");

View File

@@ -25,6 +25,7 @@ private:
void CreateSaveData(Kernel::HLERequestContext& ctx);
void MountSaveData(Kernel::HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);

View File

@@ -108,9 +108,10 @@ void Controller_NPad::OnInit() {
styleset_changed_event =
Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
if (!IsControllerActivated())
if (!IsControllerActivated()) {
return;
std::size_t controller{};
}
if (style.raw == 0) {
// We want to support all controllers
style.handheld.Assign(1);

View File

@@ -3,9 +3,13 @@
// Refer to the license.txt file included.
#include <memory>
#include <fmt/format.h>
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
#include "core/loader/nro.h"
namespace Service::LDR {
@@ -59,16 +63,58 @@ public:
explicit RelocatableObject() : ServiceFramework{"ldr:ro"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LoadNro"},
{0, &RelocatableObject::LoadNro, "LoadNro"},
{1, nullptr, "UnloadNro"},
{2, nullptr, "LoadNrr"},
{2, &RelocatableObject::LoadNrr, "LoadNrr"},
{3, nullptr, "UnloadNrr"},
{4, nullptr, "Initialize"},
{4, &RelocatableObject::Initialize, "Initialize"},
};
// clang-format on
RegisterHandlers(functions);
}
void LoadNrr(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_LDR, "(STUBBED) called");
}
void LoadNro(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
rp.Skip(2, false);
const VAddr nro_addr{rp.Pop<VAddr>()};
const u64 nro_size{rp.Pop<u64>()};
const VAddr bss_addr{rp.Pop<VAddr>()};
const u64 bss_size{rp.Pop<u64>()};
// Read NRO data from memory
std::vector<u8> nro_data(nro_size);
Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
// Load NRO as new executable module
const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)};
Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr);
// TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party.
// It is currently missing:
// - Signature checks with LoadNRR
// - Checking if a module has already been loaded
// - Using/validating BSS, etc. params (these are used from NRO header instead)
// - Error checking
// - ...Probably other things
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(addr);
LOG_WARNING(Service_LDR, "(STUBBED) called");
}
void Initialize(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_LDR, "(STUBBED) called");
}
};
void InstallInterfaces(SM::ServiceManager& sm) {

View File

@@ -328,13 +328,15 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IUser>(*this);
}
void Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
if (buffer.size() < sizeof(AmiiboFile)) {
return; // Failed to load file
return false;
}
std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
nfc_tag_load->Signal();
return true;
}
const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const {
return nfc_tag_load;

View File

@@ -32,7 +32,7 @@ public:
static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size");
void CreateUserInterface(Kernel::HLERequestContext& ctx);
void LoadAmiibo(const std::vector<u8>& buffer);
bool LoadAmiibo(const std::vector<u8>& buffer);
const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const;
const AmiiboFile& GetAmiiboBuffer() const;

View File

@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/ns/ns.h"
@@ -118,7 +121,7 @@ public:
{305, nullptr, "TerminateSystemApplet"},
{306, nullptr, "LaunchOverlayApplet"},
{307, nullptr, "TerminateOverlayApplet"},
{400, nullptr, "GetApplicationControlData"},
{400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
{401, nullptr, "InvalidateAllApplicationControlCache"},
{402, nullptr, "RequestDownloadApplicationControlData"},
{403, nullptr, "GetMaxApplicationControlCacheCount"},
@@ -243,6 +246,65 @@ public:
RegisterHandlers(functions);
}
void GetApplicationControlData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto flag = rp.PopRaw<u64>();
LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
const auto title_id = rp.PopRaw<u64>();
const auto size = ctx.GetWriteBufferSize();
const FileSys::PatchManager pm{title_id};
const auto control = pm.GetControlMetadata();
std::vector<u8> out;
if (control.first != nullptr) {
if (size < 0x4000) {
LOG_ERROR(Service_NS,
"output buffer is too small! (actual={:016X}, expected_min=0x4000)",
size);
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find a better error code for this.
rb.Push(ResultCode(-1));
return;
}
out.resize(0x4000);
const auto bytes = control.first->GetRawBytes();
std::memcpy(out.data(), bytes.data(), bytes.size());
} else {
LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
title_id);
out.resize(std::min<u64>(0x4000, size));
}
if (control.second != nullptr) {
if (size < 0x4000 + control.second->GetSize()) {
LOG_ERROR(Service_NS,
"output buffer is too small! (actual={:016X}, expected_min={:016X})",
size, 0x4000 + control.second->GetSize());
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find a better error code for this.
rb.Push(ResultCode(-1));
return;
}
out.resize(0x4000 + control.second->GetSize());
control.second->Read(out.data() + 0x4000, control.second->GetSize());
} else {
LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
title_id);
}
ctx.WriteBuffer(out);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(out.size()));
}
};
class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {

View File

@@ -67,15 +67,15 @@ public:
explicit IClientEpSession() : ServiceFramework{"IClientEpSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown1"},
{1, nullptr, "Unknown2"},
{2, nullptr, "Unknown3"},
{3, nullptr, "Unknown4"},
{0, nullptr, "Open"},
{1, nullptr, "Close"},
{2, nullptr, "Unknown1"},
{3, nullptr, "Populate"},
{4, nullptr, "PostBufferAsync"},
{5, nullptr, "Unknown5"},
{6, nullptr, "Unknown6"},
{7, nullptr, "Unknown7"},
{8, nullptr, "Unknown8"},
{5, nullptr, "GetXferReport"},
{6, nullptr, "Unknown2"},
{7, nullptr, "Unknown3"},
{8, nullptr, "Unknown4"},
};
// clang-format on
@@ -89,15 +89,15 @@ public:
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown1"},
{1, nullptr, "Unknown2"},
{2, nullptr, "Unknown3"},
{3, nullptr, "Unknown4"},
{4, nullptr, "Unknown5"},
{1, nullptr, "SetInterface"},
{2, nullptr, "GetInterface"},
{3, nullptr, "GetAlternateInterface"},
{4, nullptr, "GetCurrentFrame"},
{5, nullptr, "CtrlXferAsync"},
{6, nullptr, "Unknown6"},
{6, nullptr, "Unknown2"},
{7, nullptr, "GetCtrlXferReport"},
{8, nullptr, "Unknown7"},
{9, nullptr, "GetClientEpSession"},
{8, nullptr, "ResetDevice"},
{9, nullptr, "OpenUsbEp"},
};
// clang-format on
@@ -111,13 +111,14 @@ public:
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindClientProcess"},
{1, nullptr, "Unknown1"},
{2, nullptr, "Unknown2"},
{3, nullptr, "Unknown3"},
{4, nullptr, "Unknown4"},
{5, nullptr, "Unknown5"},
{1, nullptr, "QueryAllInterfaces"},
{2, nullptr, "QueryAvailableInterfaces"},
{3, nullptr, "QueryAcquiredInterfaces"},
{4, nullptr, "CreateInterfaceAvailableEvent"},
{5, nullptr, "DestroyInterfaceAvailableEvent"},
{6, nullptr, "GetInterfaceStateChangeEvent"},
{7, nullptr, "GetClientIfSession"},
{7, nullptr, "AcquireUsbIf"},
{8, nullptr, "Unknown1"},
};
// clang-format on

View File

@@ -12,10 +12,12 @@
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_offset.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/nro.h"
#include "core/loader/nso.h"
#include "core/memory.h"
@@ -127,18 +129,23 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file.ReadObject(&nro_header)) {
/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name,
VAddr load_base) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
// Read NSO header
NroHeader nro_header{};
std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
return {};
}
// Build program image
std::vector<u8> program_image = file.ReadBytes(PageAlignSize(nro_header.file_size));
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
std::memcpy(program_image.data(), data.data(), program_image.size());
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
}
@@ -182,11 +189,15 @@ bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
GDBStub::RegisterModule(name, load_base, load_base);
return true;
}
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base);
}
ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
@@ -199,6 +210,9 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
return ResultStatus::ErrorLoadingNRO;
}
if (romfs != nullptr)
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;

View File

@@ -5,6 +5,7 @@
#pragma once
#include <string>
#include <vector>
#include "common/common_types.h"
#include "core/loader/linker.h"
#include "core/loader/loader.h"
@@ -40,6 +41,8 @@ public:
ResultStatus ReadTitle(std::string& title) override;
bool IsRomFSUpdatable() const override;
static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base);
private:
bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);

View File

@@ -36,6 +36,16 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
std::tie(nacp_file, icon_file) =
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
if (nsp->IsExtractedType()) {
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
} else {
if (title_id == 0)
return;
secondary_loader = std::make_unique<AppLoader_NCA>(
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
}
}
AppLoader_NSP::~AppLoader_NSP() = default;
@@ -67,26 +77,19 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (nsp->IsExtractedType()) {
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
} else {
if (title_id == 0)
return ResultStatus::ErrorNSPMissingProgramNCA;
if (title_id == 0)
return ResultStatus::ErrorNSPMissingProgramNCA;
secondary_loader = std::make_unique<AppLoader_NCA>(
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
if (nsp->GetStatus() != ResultStatus::Success)
return nsp->GetStatus();
if (nsp->GetStatus() != ResultStatus::Success)
return nsp->GetStatus();
if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
return nsp->GetProgramStatus(title_id);
if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
return nsp->GetProgramStatus(title_id);
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
if (!Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
return ResultStatus::ErrorNSPMissingProgramNCA;
}
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
if (!Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
return ResultStatus::ErrorNSPMissingProgramNCA;
}
const auto result = secondary_loader->Load(process);

View File

@@ -74,10 +74,6 @@ double PerfStats::GetLastFrameTimeScale() {
}
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
// values increase the time needed to recover and limit framerate again after spikes.
constexpr microseconds MAX_LAG_TIME_US = 25000us;
if (!Settings::values.use_frame_limit) {
return;
}

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
#include <cstring>
#include "common/assert.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -19,7 +20,24 @@ namespace Tegra::Engines {
constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
: memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {}
: memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {
InitializeRegisterDefaults();
}
void Maxwell3D::InitializeRegisterDefaults() {
// Initializes registers to their default values - what games expect them to be at boot. This is
// for certain registers that may not be explicitly set by games.
// Reset all registers to zero
std::memset(&regs, 0, sizeof(regs));
// Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is
// needed for ARMS.
for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) {
regs.viewport[viewport].depth_range_near = 0.0f;
regs.viewport[viewport].depth_range_far = 1.0f;
}
}
void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
// Reset the current macro.
@@ -155,7 +173,6 @@ void Maxwell3D::ProcessQueryGet() {
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
"Units other than CROP are unimplemented");
u32 value = Memory::Read32(*address);
u64 result = 0;
// TODO(Subv): Support the other query variables

View File

@@ -723,7 +723,11 @@ public:
StencilOp stencil_back_op_zpass;
ComparisonOp stencil_back_func_func;
INSERT_PADDING_WORDS(0x17);
INSERT_PADDING_WORDS(0x4);
u32 framebuffer_srgb;
INSERT_PADDING_WORDS(0x12);
union {
BitField<2, 1, u32> coord_origin;
@@ -751,7 +755,14 @@ public:
};
} draw;
INSERT_PADDING_WORDS(0x6B);
INSERT_PADDING_WORDS(0xA);
struct {
u32 enabled;
u32 index;
} primitive_restart;
INSERT_PADDING_WORDS(0x5F);
struct {
u32 start_addr_high;
@@ -984,6 +995,8 @@ public:
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
private:
void InitializeRegisterDefaults();
VideoCore::RasterizerInterface& rasterizer;
std::unordered_map<u32, std::vector<u32>> uploaded_macros;
@@ -1077,9 +1090,11 @@ ASSERT_REG_POSITION(stencil_back_op_fail, 0x566);
ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567);
ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568);
ASSERT_REG_POSITION(stencil_back_func_func, 0x569);
ASSERT_REG_POSITION(framebuffer_srgb, 0x56E);
ASSERT_REG_POSITION(point_coord_replace, 0x581);
ASSERT_REG_POSITION(code_address, 0x582);
ASSERT_REG_POSITION(draw, 0x585);
ASSERT_REG_POSITION(primitive_restart, 0x591);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(cull, 0x646);

View File

@@ -79,6 +79,7 @@ union Attribute {
constexpr explicit Attribute(u64 value) : value(value) {}
enum class Index : u64 {
PointSize = 6,
Position = 7,
Attribute_0 = 8,
Attribute_31 = 39,
@@ -207,6 +208,16 @@ enum class UniformType : u64 {
Double = 5,
};
enum class StoreType : u64 {
Unsigned8 = 0,
Signed8 = 1,
Unsigned16 = 2,
Signed16 = 3,
Bytes32 = 4,
Bytes64 = 5,
Bytes128 = 6,
};
enum class IMinMaxExchange : u64 {
None = 0,
XLo = 1,
@@ -746,6 +757,18 @@ union Instruction {
BitField<44, 2, u64> unknown;
} ld_c;
union {
BitField<48, 3, StoreType> type;
} ldst_sl;
union {
BitField<44, 2, u64> unknown;
} ld_l;
union {
BitField<44, 2, u64> unknown;
} st_l;
union {
BitField<0, 3, u64> pred0;
BitField<3, 3, u64> pred3;
@@ -1208,6 +1231,7 @@ union Instruction {
BitField<61, 1, u64> is_b_imm;
BitField<60, 1, u64> is_b_gpr;
BitField<59, 1, u64> is_c_gpr;
BitField<20, 24, s64> smem_imm;
Attribute attribute;
Sampler sampler;
@@ -1231,8 +1255,12 @@ public:
BRA,
PBK,
LD_A,
LD_L,
LD_S,
LD_C,
ST_A,
ST_L,
ST_S,
LDG, // Load from global memory
STG, // Store in global memory
TEX,
@@ -1489,8 +1517,12 @@ private:
INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),

View File

@@ -96,6 +96,11 @@ struct Header {
}
} ps;
};
u64 GetLocalMemorySize() {
return (common1.shader_local_memory_low_size |
(common2.shader_local_memory_high_size << 24));
}
};
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");

View File

@@ -418,6 +418,7 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// Bind the framebuffer surfaces
state.draw.draw_framebuffer = framebuffer.handle;
state.Apply();
state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0;
if (using_color_fb) {
if (single_color_target) {
@@ -429,6 +430,9 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// 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);
// Workaround for and issue in nvidia drivers
// https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
}
glFramebufferTexture2D(
@@ -446,6 +450,11 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// 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);
// Enable sRGB only for supported formats
// Workaround for and issue in nvidia drivers
// https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
state.framebuffer_srgb.enabled |=
color_surface->GetSurfaceParams().srgb_conversion;
}
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
@@ -537,7 +546,9 @@ void RasterizerOpenGL::Clear() {
ConfigureFramebuffers(use_color, use_depth || use_stencil, false,
regs.clear_buffers.RT.Value());
// Copy the sRGB setting to the clear state to avoid problem with
// specific driver implementations
clear_state.framebuffer_srgb.enabled = state.framebuffer_srgb.enabled;
clear_state.Apply();
if (use_color) {
@@ -570,6 +581,8 @@ void RasterizerOpenGL::DrawArrays() {
SyncBlendState();
SyncLogicOpState();
SyncCullMode();
SyncPrimitiveRestart();
SyncDepthRange();
SyncScissorTest();
// Alpha Testing is synced on shaders.
SyncTransformFeedback();
@@ -923,12 +936,18 @@ void RasterizerOpenGL::SyncCullMode() {
}
}
void RasterizerOpenGL::SyncDepthScale() {
UNREACHABLE();
void RasterizerOpenGL::SyncPrimitiveRestart() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
state.primitive_restart.enabled = regs.primitive_restart.enabled;
state.primitive_restart.index = regs.primitive_restart.index;
}
void RasterizerOpenGL::SyncDepthOffset() {
UNREACHABLE();
void RasterizerOpenGL::SyncDepthRange() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
state.depth.depth_range_near = regs.viewport->depth_range_near;
state.depth.depth_range_far = regs.viewport->depth_range_far;
}
void RasterizerOpenGL::SyncDepthTestState() {

View File

@@ -144,11 +144,11 @@ private:
/// Syncs the cull mode to match the guest state
void SyncCullMode();
/// Syncs the depth scale to match the guest state
void SyncDepthScale();
/// Syncs the primitve restart to match the guest state
void SyncPrimitiveRestart();
/// Syncs the depth offset to match the guest state
void SyncDepthOffset();
/// Syncs the depth range to match the guest state
void SyncDepthRange();
/// Syncs the depth test state to match the guest state
void SyncDepthTestState();

View File

@@ -40,6 +40,10 @@ static bool IsPixelFormatASTC(PixelFormat format) {
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
return true;
default:
return false;
@@ -56,6 +60,14 @@ static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
return {8, 8};
case PixelFormat::ASTC_2D_8X5:
return {8, 5};
case PixelFormat::ASTC_2D_4X4_SRGB:
return {4, 4};
case PixelFormat::ASTC_2D_5X4_SRGB:
return {5, 4};
case PixelFormat::ASTC_2D_8X8_SRGB:
return {8, 8};
case PixelFormat::ASTC_2D_8X5_SRGB:
return {8, 5};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
@@ -78,6 +90,29 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
}
}
std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const {
const u32 compression_factor{GetCompressionFactor(pixel_format)};
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
u32 m_depth = (layer_only ? 1U : depth);
u32 m_width = std::max(1U, width / compression_factor);
u32 m_height = std::max(1U, height / compression_factor);
std::size_t size = Tegra::Texture::CalculateSize(is_tiled, bytes_per_pixel, m_width, m_height,
m_depth, block_height, block_depth);
u32 m_block_height = block_height;
u32 m_block_depth = block_depth;
std::size_t block_size_bytes = 512 * block_height * block_depth; // 512 is GOB size
for (u32 i = 1; i < max_mip_level; i++) {
m_width = std::max(1U, m_width / 2);
m_height = std::max(1U, m_height / 2);
m_depth = std::max(1U, m_depth / 2);
m_block_height = std::max(1U, m_block_height / 2);
m_block_depth = std::max(1U, m_block_depth / 2);
size += Tegra::Texture::CalculateSize(is_tiled, bytes_per_pixel, m_width, m_height, m_depth,
m_block_height, m_block_depth);
}
return is_tiled ? Common::AlignUp(size, block_size_bytes) : size;
}
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
SurfaceParams params{};
@@ -85,8 +120,9 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
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.srgb_conversion = config.tic.IsSrgbConversionEnabled();
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
params.type = GetFormatType(params.pixel_format);
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
@@ -124,6 +160,7 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
break;
}
params.is_layered = SurfaceTargetIsLayered(params.target);
params.max_mip_level = config.tic.max_mip_level + 1;
params.rt = {};
@@ -142,6 +179,8 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
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.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
@@ -150,6 +189,7 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.max_mip_level = 0;
params.is_layered = false;
// Render target specific parameters, not used for caching
params.rt.index = static_cast<u32>(index);
@@ -176,12 +216,14 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
params.pixel_format = PixelFormatFromDepthFormat(format);
params.component_type = ComponentTypeFromDepthFormat(format);
params.type = GetFormatType(params.pixel_format);
params.srgb_conversion = false;
params.width = zeta_width;
params.height = zeta_height;
params.unaligned_height = zeta_height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.max_mip_level = 0;
params.is_layered = false;
params.rt = {};
params.InitCacheParameters(zeta_address);
@@ -198,6 +240,8 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
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.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
@@ -229,7 +273,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI
{GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT1
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT23
@@ -263,14 +307,29 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I
{GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S
{GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // SRGBA8
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
{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
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm,
false}, // RGBA8_SRGB
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
{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
{GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8
// Compressed sRGB formats
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT1_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT23_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT45_SRGB
{GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8,
ComponentType::UNorm, true}, // BC7U_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB
// Depth formats
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
@@ -335,6 +394,10 @@ static bool IsFormatBCn(PixelFormat format) {
case PixelFormat::BC7U:
case PixelFormat::BC6H_UF16:
case PixelFormat::BC6H_SF16:
case PixelFormat::DXT1_SRGB:
case PixelFormat::DXT23_SRGB:
case PixelFormat::DXT45_SRGB:
case PixelFormat::BC7U_SRGB:
return true;
}
return false;
@@ -361,10 +424,11 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 d
}
}
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
SurfaceParams::MaxPixelFormat>
morton_to_gl_fns = {
// clang-format off
using GLConversionArray = std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
SurfaceParams::MaxPixelFormat>;
static constexpr GLConversionArray morton_to_gl_fns = {
// clang-format off
MortonCopy<true, PixelFormat::ABGR8U>,
MortonCopy<true, PixelFormat::ABGR8S>,
MortonCopy<true, PixelFormat::ABGR8UI>,
@@ -405,7 +469,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
MortonCopy<true, PixelFormat::RG16I>,
MortonCopy<true, PixelFormat::RG16S>,
MortonCopy<true, PixelFormat::RGB32F>,
MortonCopy<true, PixelFormat::SRGBA8>,
MortonCopy<true, PixelFormat::RGBA8_SRGB>,
MortonCopy<true, PixelFormat::RG8U>,
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::RG32UI>,
@@ -413,18 +477,25 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
MortonCopy<true, PixelFormat::BGRA8_SRGB>,
MortonCopy<true, PixelFormat::DXT1_SRGB>,
MortonCopy<true, PixelFormat::DXT23_SRGB>,
MortonCopy<true, PixelFormat::DXT45_SRGB>,
MortonCopy<true, PixelFormat::BC7U_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z24S8>,
MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32FS8>,
// clang-format on
// clang-format on
};
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
SurfaceParams::MaxPixelFormat>
gl_to_morton_fns = {
// clang-format off
static constexpr GLConversionArray gl_to_morton_fns = {
// clang-format off
MortonCopy<false, PixelFormat::ABGR8U>,
MortonCopy<false, PixelFormat::ABGR8S>,
MortonCopy<false, PixelFormat::ABGR8UI>,
@@ -466,7 +537,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
MortonCopy<false, PixelFormat::RG16I>,
MortonCopy<false, PixelFormat::RG16S>,
MortonCopy<false, PixelFormat::RGB32F>,
MortonCopy<false, PixelFormat::SRGBA8>,
MortonCopy<false, PixelFormat::RGBA8_SRGB>,
MortonCopy<false, PixelFormat::RG8U>,
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::RG32UI>,
@@ -474,14 +545,49 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::BGRA8_SRGB>,
MortonCopy<false, PixelFormat::DXT1_SRGB>,
MortonCopy<false, PixelFormat::DXT23_SRGB>,
MortonCopy<false, PixelFormat::DXT45_SRGB>,
MortonCopy<false, PixelFormat::BC7U_SRGB>,
nullptr,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z24S8>,
MortonCopy<false, PixelFormat::S8Z24>,
MortonCopy<false, PixelFormat::Z32FS8>,
// clang-format on
// clang-format on
};
void SwizzleFunc(const GLConversionArray& functions, const SurfaceParams& params,
std::vector<u8>& gl_buffer) {
u32 depth = params.depth;
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
depth = 1U;
}
if (params.is_layered) {
u64 offset = 0;
u64 offset_gl = 0;
u64 layer_size = params.LayerMemorySize();
u64 gl_size = params.LayerSizeGL();
for (u32 i = 0; i < depth; i++) {
functions[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, params.block_depth, 1,
gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
offset += layer_size;
offset_gl += gl_size;
}
} else {
functions[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, params.block_depth, depth,
gl_buffer.data(), gl_buffer.size(), params.addr);
}
}
static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
GLuint read_fb_handle, GLuint draw_fb_handle, GLenum src_attachment = 0,
GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
@@ -495,6 +601,8 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
OpenGLState state;
state.draw.read_framebuffer = read_fb_handle;
state.draw.draw_framebuffer = draw_fb_handle;
// Set sRGB enabled if the destination surfaces need it
state.framebuffer_srgb.enabled = dst_params.srgb_conversion;
state.Apply();
u32 buffers{};
@@ -830,7 +938,11 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
case PixelFormat::ASTC_2D_5X4: {
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB: {
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
u32 block_width{};
u32 block_height{};
@@ -862,7 +974,9 @@ static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelForm
case PixelFormat::G8R8U:
case PixelFormat::G8R8S:
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8: {
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB: {
LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
static_cast<u32>(pixel_format));
UNREACHABLE();
@@ -881,21 +995,10 @@ void CachedSurface::LoadGLBuffer() {
gl_buffer.resize(params.size_in_bytes_gl);
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;
}
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);
SwizzleFunc(morton_to_gl_fns, params, gl_buffer);
} else {
const auto texture_src_data{Memory::GetPointer(params.addr)};
const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
@@ -920,8 +1023,8 @@ void CachedSurface::FlushGLBuffer() {
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());
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type,
static_cast<GLsizei>(gl_buffer.size()), gl_buffer.data());
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
params.height);
@@ -929,19 +1032,10 @@ void CachedSurface::FlushGLBuffer() {
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;
}
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());
SwizzleFunc(gl_to_morton_fns, params, gl_buffer);
} else {
std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes());
}
@@ -1179,7 +1273,7 @@ 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);
FlushRegion(src_params.addr, dst_params.MemorySize());
LoadSurface(dst_surface);
}
@@ -1221,44 +1315,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
case SurfaceParams::SurfaceTarget::TextureCubemap:
case SurfaceParams::SurfaceTarget::Texture3D:
AccurateCopySurface(old_surface, new_surface);
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;
}
break;
}
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(new_params.target));
@@ -1266,7 +1326,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
}
return new_surface;
}
} // namespace OpenGL
Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const {
return TryGet(addr);

View File

@@ -69,7 +69,7 @@ struct SurfaceParams {
RG16I = 37,
RG16S = 38,
RGB32F = 39,
SRGBA8 = 40,
RGBA8_SRGB = 40,
RG8U = 41,
RG8S = 42,
RG32UI = 43,
@@ -77,19 +77,28 @@ struct SurfaceParams {
ASTC_2D_8X8 = 45,
ASTC_2D_8X5 = 46,
ASTC_2D_5X4 = 47,
BGRA8_SRGB = 48,
DXT1_SRGB = 49,
DXT23_SRGB = 50,
DXT45_SRGB = 51,
BC7U_SRGB = 52,
ASTC_2D_4X4_SRGB = 53,
ASTC_2D_8X8_SRGB = 54,
ASTC_2D_8X5_SRGB = 55,
ASTC_2D_5X4_SRGB = 56,
MaxColorFormat,
// Depth formats
Z32F = 48,
Z16 = 49,
Z32F = 57,
Z16 = 58,
MaxDepthFormat,
// DepthStencil formats
Z24S8 = 50,
S8Z24 = 51,
Z32FS8 = 52,
Z24S8 = 59,
S8Z24 = 60,
Z32FS8 = 61,
MaxDepthStencilFormat,
@@ -168,6 +177,23 @@ struct SurfaceParams {
}
}
static bool SurfaceTargetIsLayered(SurfaceTarget target) {
switch (target) {
case SurfaceTarget::Texture1D:
case SurfaceTarget::Texture2D:
case SurfaceTarget::Texture3D:
return false;
case SurfaceTarget::Texture1DArray:
case SurfaceTarget::Texture2DArray:
case SurfaceTarget::TextureCubemap:
return true;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
UNREACHABLE();
return false;
}
}
/**
* Gets the compression factor for the specified PixelFormat. This applies to just the
* "compressed width" and "compressed height", not the overall compression factor of a
@@ -219,7 +245,7 @@ struct SurfaceParams {
1, // RG16I
1, // RG16S
1, // RGB32F
1, // SRGBA8
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
@@ -227,6 +253,15 @@ struct SurfaceParams {
4, // ASTC_2D_8X8
4, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
4, // ASTC_2D_8X8_SRGB
4, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
@@ -238,6 +273,77 @@ struct SurfaceParams {
return compression_factor_table[static_cast<std::size_t>(format)];
}
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
5, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
return block_height_table[static_cast<std::size_t>(format)];
}
static constexpr u32 GetFormatBpp(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
@@ -283,14 +389,23 @@ struct SurfaceParams {
32, // RG16I
32, // RG16S
96, // RGB32F
32, // SRGBA8
32, // RGBA8_SRGB
16, // RG8U
16, // RG8S
64, // RG32UI
32, // R32UI
16, // ASTC_2D_8X8
32, // ASTC_2D_8X5
16, // ASTC_2D_8X5
32, // ASTC_2D_5X4
32, // BGRA8_SRGB
64, // DXT1_SRGB
128, // DXT23_SRGB
128, // DXT45_SRGB
128, // BC7U
32, // ASTC_2D_4X4_SRGB
16, // ASTC_2D_8X8_SRGB
16, // ASTC_2D_8X5_SRGB
32, // ASTC_2D_5X4_SRGB
32, // Z32F
16, // Z16
32, // Z24S8
@@ -329,6 +444,7 @@ struct SurfaceParams {
// TODO (Hexagon12): Converting SRGBA to RGBA is a hack and doesn't completely correct the
// gamma.
case Tegra::RenderTargetFormat::RGBA8_SRGB:
return PixelFormat::RGBA8_SRGB;
case Tegra::RenderTargetFormat::RGBA8_UNORM:
return PixelFormat::ABGR8U;
case Tegra::RenderTargetFormat::RGBA8_SNORM:
@@ -336,6 +452,7 @@ struct SurfaceParams {
case Tegra::RenderTargetFormat::RGBA8_UINT:
return PixelFormat::ABGR8UI;
case Tegra::RenderTargetFormat::BGRA8_SRGB:
return PixelFormat::BGRA8_SRGB;
case Tegra::RenderTargetFormat::BGRA8_UNORM:
return PixelFormat::BGRA8;
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
@@ -399,10 +516,14 @@ struct SurfaceParams {
}
static PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
Tegra::Texture::ComponentType component_type) {
Tegra::Texture::ComponentType component_type,
bool is_srgb) {
// TODO(Subv): Properly implement this
switch (format) {
case Tegra::Texture::TextureFormat::A8R8G8B8:
if (is_srgb) {
return PixelFormat::RGBA8_SRGB;
}
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::ABGR8U;
@@ -537,11 +658,11 @@ struct SurfaceParams {
case Tegra::Texture::TextureFormat::Z24S8:
return PixelFormat::Z24S8;
case Tegra::Texture::TextureFormat::DXT1:
return PixelFormat::DXT1;
return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
case Tegra::Texture::TextureFormat::DXT23:
return PixelFormat::DXT23;
return is_srgb ? PixelFormat::DXT23_SRGB : PixelFormat::DXT23;
case Tegra::Texture::TextureFormat::DXT45:
return PixelFormat::DXT45;
return is_srgb ? PixelFormat::DXT45_SRGB : PixelFormat::DXT45;
case Tegra::Texture::TextureFormat::DXN1:
return PixelFormat::DXN1;
case Tegra::Texture::TextureFormat::DXN2:
@@ -555,19 +676,19 @@ struct SurfaceParams {
static_cast<u32>(component_type));
UNREACHABLE();
case Tegra::Texture::TextureFormat::BC7U:
return PixelFormat::BC7U;
return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U;
case Tegra::Texture::TextureFormat::BC6H_UF16:
return PixelFormat::BC6H_UF16;
case Tegra::Texture::TextureFormat::BC6H_SF16:
return PixelFormat::BC6H_SF16;
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
return PixelFormat::ASTC_2D_4X4;
return is_srgb ? PixelFormat::ASTC_2D_4X4_SRGB : PixelFormat::ASTC_2D_4X4;
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
return PixelFormat::ASTC_2D_5X4;
return is_srgb ? PixelFormat::ASTC_2D_5X4_SRGB : PixelFormat::ASTC_2D_5X4;
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
return PixelFormat::ASTC_2D_8X8;
return is_srgb ? PixelFormat::ASTC_2D_8X8_SRGB : PixelFormat::ASTC_2D_8X8;
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
return PixelFormat::ASTC_2D_8X5;
return is_srgb ? PixelFormat::ASTC_2D_8X5_SRGB : PixelFormat::ASTC_2D_8X5;
case Tegra::Texture::TextureFormat::R16_G16:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
@@ -742,6 +863,25 @@ struct SurfaceParams {
return size_in_bytes_gl / 6;
}
/// Returns the exact size of memory occupied by the texture in VRAM, including mipmaps.
std::size_t MemorySize() const {
std::size_t size = InnerMemorySize(is_layered);
if (is_layered)
return size * depth;
return size;
}
/// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including
/// mipmaps.
std::size_t LayerMemorySize() const {
return InnerMemorySize(true);
}
/// Returns the size of a layer of this surface in OpenGL.
std::size_t LayerSizeGL() const {
return SizeInBytesRaw(true) / depth;
}
/// Creates SurfaceParams from a texture configuration
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
const GLShader::SamplerEntry& entry);
@@ -782,7 +922,8 @@ struct SurfaceParams {
u32 unaligned_height;
SurfaceTarget target;
u32 max_mip_level;
bool is_layered;
bool srgb_conversion;
// Parameters used for caching
VAddr addr;
Tegra::GPUVAddr gpu_addr;
@@ -797,6 +938,9 @@ struct SurfaceParams {
u32 layer_stride;
u32 base_layer;
} rt;
private:
std::size_t InnerMemorySize(bool layer_only = false) const;
};
}; // namespace OpenGL

View File

@@ -6,6 +6,7 @@
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <boost/optional.hpp>
#include <fmt/format.h>
@@ -276,7 +277,8 @@ public:
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
const Tegra::Shader::Header& header)
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} {
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header},
fixed_pipeline_output_attributes_used{}, local_memory_size{0} {
BuildRegisterList();
BuildInputList();
}
@@ -339,10 +341,10 @@ public:
*/
void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value,
u64 dest_num_components, u64 value_num_components,
bool is_saturated = false, u64 dest_elem = 0) {
bool is_saturated = false, u64 dest_elem = 0, bool precise = false) {
SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value,
dest_num_components, value_num_components, dest_elem);
dest_num_components, value_num_components, dest_elem, precise);
}
/**
@@ -366,7 +368,7 @@ public:
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')',
dest_num_components, value_num_components, dest_elem);
dest_num_components, value_num_components, dest_elem, false);
if (sets_cc) {
const std::string zero_condition = "( " + ConvertIntegerSize(value, size) + " == 0 )";
@@ -414,7 +416,7 @@ public:
}
}();
SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem);
SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem, false);
}
/**
@@ -434,6 +436,25 @@ public:
shader.AddLine(dest + " = " + src + ';');
}
std::string GetLocalMemoryAsFloat(const std::string& index) {
return "lmem[" + index + ']';
}
std::string GetLocalMemoryAsInteger(const std::string& index, bool is_signed = false) {
const std::string func{is_signed ? "floatToIntBits" : "floatBitsToUint"};
return func + "(lmem[" + index + "])";
}
void SetLocalMemoryAsFloat(const std::string& index, const std::string& value) {
shader.AddLine("lmem[" + index + "] = " + value + ';');
}
void SetLocalMemoryAsInteger(const std::string& index, const std::string& value,
bool is_signed = false) {
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
shader.AddLine("lmem[" + index + "] = " + func + '(' + value + ");");
}
std::string GetControlCode(const Tegra::Shader::ControlCode cc) const {
switch (cc) {
case Tegra::Shader::ControlCode::NEU:
@@ -480,7 +501,12 @@ public:
std::to_string(static_cast<u32>(attribute)) + ']' +
GetSwizzle(elem) + " = " + src + ';');
} else {
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
if (attribute == Attribute::Index::PointSize) {
fixed_pipeline_output_attributes_used.insert(attribute);
shader.AddLine(dest + " = " + src + ';');
} else {
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
}
}
}
}
@@ -524,7 +550,9 @@ public:
/// Add declarations.
void GenerateDeclarations(const std::string& suffix) {
GenerateVertex();
GenerateRegisters(suffix);
GenerateLocalMemory();
GenerateInternalFlags();
GenerateInputAttrs();
GenerateOutputAttrs();
@@ -570,6 +598,10 @@ public:
return entry.GetName();
}
void SetLocalMemory(u64 lmem) {
local_memory_size = lmem;
}
private:
/// Generates declarations for registers.
void GenerateRegisters(const std::string& suffix) {
@@ -580,6 +612,15 @@ private:
declarations.AddNewLine();
}
/// Generates declarations for local memory.
void GenerateLocalMemory() {
if (local_memory_size > 0) {
declarations.AddLine("float lmem[" + std::to_string((local_memory_size - 1 + 4) / 4) +
"];");
declarations.AddNewLine();
}
}
/// Generates declarations for internal flags.
void GenerateInternalFlags() {
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
@@ -683,6 +724,20 @@ private:
declarations.AddNewLine();
}
void GenerateVertex() {
if (stage != Maxwell3D::Regs::ShaderStage::Vertex)
return;
declarations.AddLine("out gl_PerVertex {");
++declarations.scope;
declarations.AddLine("vec4 gl_Position;");
for (auto& o : fixed_pipeline_output_attributes_used) {
if (o == Attribute::Index::PointSize)
declarations.AddLine("float gl_PointSize;");
}
--declarations.scope;
declarations.AddLine("};");
}
/// Generates code representing a temporary (GPR) register.
std::string GetRegister(const Register& reg, unsigned elem) {
if (reg == Register::ZeroIndex) {
@@ -702,7 +757,8 @@ private:
* @param dest_elem Optional, the destination element to use for the operation.
*/
void SetRegister(const Register& reg, u64 elem, const std::string& value,
u64 dest_num_components, u64 value_num_components, u64 dest_elem) {
u64 dest_num_components, u64 value_num_components, u64 dest_elem,
bool precise) {
if (reg == Register::ZeroIndex) {
LOG_CRITICAL(HW_GPU, "Cannot set Register::ZeroIndex");
UNREACHABLE();
@@ -719,7 +775,18 @@ private:
src += GetSwizzle(elem);
}
shader.AddLine(dest + " = " + src + ';');
if (precise && stage != Maxwell3D::Regs::ShaderStage::Fragment) {
shader.AddLine('{');
++shader.scope;
// This avoids optimizations of constant propagation and keeps the code as the original
// Sadly using the precise keyword causes "linking" errors on fragment shaders.
shader.AddLine("precise float tmp = " + src + ';');
shader.AddLine(dest + " = tmp;");
--shader.scope;
shader.AddLine('}');
} else {
shader.AddLine(dest + " = " + src + ';');
}
}
/// Build the GLSL register list.
@@ -836,6 +903,8 @@ private:
/// Generates code representing the declaration name of an output attribute register.
std::string GetOutputAttribute(Attribute::Index attribute) {
switch (attribute) {
case Attribute::Index::PointSize:
return "gl_PointSize";
case Attribute::Index::Position:
return "position";
default:
@@ -870,6 +939,8 @@ private:
const Maxwell3D::Regs::ShaderStage& stage;
const std::string& suffix;
const Tegra::Shader::Header& header;
std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used;
u64 local_memory_size;
};
class GLSLGenerator {
@@ -879,6 +950,8 @@ public:
: subroutines(subroutines), program_code(program_code), main_offset(main_offset),
stage(stage), suffix(suffix) {
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
local_memory_size = header.GetLocalMemorySize();
regs.SetLocalMemory(local_memory_size);
Generate(suffix);
}
@@ -1449,8 +1522,9 @@ private:
ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented");
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Id::FADD_C:
@@ -1458,8 +1532,9 @@ private:
case OpCode::Id::FADD_IMM: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Id::MUFU: {
@@ -1467,31 +1542,31 @@ private:
switch (instr.sub_op) {
case SubOp::Cos:
regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Sin:
regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Ex2:
regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Lg2:
regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Rcp:
regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Rsq:
regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Sqrt:
regs.SetRegisterToFloat(instr.gpr0, 0, "sqrt(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
default:
LOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}",
@@ -1512,7 +1587,7 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0,
'(' + condition + ") ? min(" + parameters + ") : max(" +
parameters + ')',
1, 1);
1, 1, false, 0, true);
break;
}
case OpCode::Id::RRO_C:
@@ -1541,7 +1616,7 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0,
regs.GetRegisterAsFloat(instr.gpr8) + " * " +
GetImmediate32(instr),
1, 1, instr.fmul32.saturate);
1, 1, instr.fmul32.saturate, 0, true);
break;
}
case OpCode::Id::FADD32I: {
@@ -1564,7 +1639,7 @@ private:
op_b = "-(" + op_b + ')';
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, false, 0, true);
break;
}
}
@@ -2026,8 +2101,9 @@ private:
}
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1,
instr.alu.saturate_d);
regs.SetRegisterToFloat(instr.gpr0, 0, "fma(" + op_a + ", " + op_b + ", " + op_c + ')',
1, 1, instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Type::Hfma2: {
@@ -2299,6 +2375,39 @@ private:
shader.AddLine("}");
break;
}
case OpCode::Id::LD_L: {
// Add an extra scope and declare the index register inside to prevent
// overwriting it in case it is used as an output of the LD instruction.
shader.AddLine('{');
++shader.scope;
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
std::to_string(instr.smem_imm.Value()) + ')';
shader.AddLine("uint index = (" + op + " / 4);");
const std::string op_a = regs.GetLocalMemoryAsFloat("index");
if (instr.ld_l.unknown != 1) {
LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}",
static_cast<unsigned>(instr.ld_l.unknown.Value()));
UNREACHABLE();
}
switch (instr.ldst_sl.type.Value()) {
case Tegra::Shader::StoreType::Bytes32:
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
break;
default:
LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}",
static_cast<unsigned>(instr.ldst_sl.type.Value()));
UNREACHABLE();
}
--shader.scope;
shader.AddLine('}');
break;
}
case OpCode::Id::ST_A: {
ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
"Indirect attribute loads are not supported");
@@ -2327,6 +2436,37 @@ private:
break;
}
case OpCode::Id::ST_L: {
// Add an extra scope and declare the index register inside to prevent
// overwriting it in case it is used as an output of the LD instruction.
shader.AddLine('{');
++shader.scope;
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
std::to_string(instr.smem_imm.Value()) + ')';
shader.AddLine("uint index = (" + op + " / 4);");
if (instr.st_l.unknown != 0) {
LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}",
static_cast<unsigned>(instr.st_l.unknown.Value()));
UNREACHABLE();
}
switch (instr.ldst_sl.type.Value()) {
case Tegra::Shader::StoreType::Bytes32:
regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0));
break;
default:
LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}",
static_cast<unsigned>(instr.ldst_sl.type.Value()));
UNREACHABLE();
}
--shader.scope;
shader.AddLine('}');
break;
}
case OpCode::Id::TEX: {
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
std::string coord;
@@ -3550,6 +3690,7 @@ private:
const u32 main_offset;
Maxwell3D::Regs::ShaderStage stage;
const std::string& suffix;
u64 local_memory_size;
ShaderWriter shader;
ShaderWriter declarations;

View File

@@ -19,9 +19,6 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
out += Decompiler::GetCommonDeclarations();
out += R"(
out gl_PerVertex {
vec4 gl_Position;
};
layout (location = 0) out vec4 position;

View File

@@ -11,9 +11,10 @@
namespace OpenGL {
OpenGLState OpenGLState::cur_state;
bool OpenGLState::s_rgb_used;
OpenGLState::OpenGLState() {
// These all match default OpenGL values
framebuffer_srgb.enabled = false;
cull.enabled = false;
cull.mode = GL_BACK;
cull.front_face = GL_CCW;
@@ -21,6 +22,11 @@ OpenGLState::OpenGLState() {
depth.test_enabled = false;
depth.test_func = GL_LESS;
depth.write_mask = GL_TRUE;
depth.depth_range_near = 0.0f;
depth.depth_range_far = 1.0f;
primitive_restart.enabled = false;
primitive_restart.index = 0;
color_mask.red_enabled = GL_TRUE;
color_mask.green_enabled = GL_TRUE;
@@ -84,6 +90,16 @@ OpenGLState::OpenGLState() {
}
void OpenGLState::Apply() const {
// sRGB
if (framebuffer_srgb.enabled != cur_state.framebuffer_srgb.enabled) {
if (framebuffer_srgb.enabled) {
// Track if sRGB is used
s_rgb_used = true;
glEnable(GL_FRAMEBUFFER_SRGB);
} else {
glDisable(GL_FRAMEBUFFER_SRGB);
}
}
// Culling
if (cull.enabled != cur_state.cull.enabled) {
if (cull.enabled) {
@@ -119,6 +135,24 @@ void OpenGLState::Apply() const {
glDepthMask(depth.write_mask);
}
// Depth range
if (depth.depth_range_near != cur_state.depth.depth_range_near ||
depth.depth_range_far != cur_state.depth.depth_range_far) {
glDepthRange(depth.depth_range_near, depth.depth_range_far);
}
// Primitive restart
if (primitive_restart.enabled != cur_state.primitive_restart.enabled) {
if (primitive_restart.enabled) {
glEnable(GL_PRIMITIVE_RESTART);
} else {
glDisable(GL_PRIMITIVE_RESTART);
}
}
if (primitive_restart.index != cur_state.primitive_restart.index) {
glPrimitiveRestartIndex(primitive_restart.index);
}
// Color mask
if (color_mask.red_enabled != cur_state.color_mask.red_enabled ||
color_mask.green_enabled != cur_state.color_mask.green_enabled ||

View File

@@ -35,6 +35,10 @@ constexpr TextureUnit ProcTexDiffLUT{9};
class OpenGLState {
public:
struct {
bool enabled; // GL_FRAMEBUFFER_SRGB
} framebuffer_srgb;
struct {
bool enabled; // GL_CULL_FACE
GLenum mode; // GL_CULL_FACE_MODE
@@ -42,11 +46,18 @@ public:
} cull;
struct {
bool test_enabled; // GL_DEPTH_TEST
GLenum test_func; // GL_DEPTH_FUNC
GLboolean write_mask; // GL_DEPTH_WRITEMASK
bool test_enabled; // GL_DEPTH_TEST
GLenum test_func; // GL_DEPTH_FUNC
GLboolean write_mask; // GL_DEPTH_WRITEMASK
GLfloat depth_range_near; // GL_DEPTH_RANGE
GLfloat depth_range_far; // GL_DEPTH_RANGE
} depth;
struct {
bool enabled;
GLuint index;
} primitive_restart; // GL_PRIMITIVE_RESTART
struct {
GLboolean red_enabled;
GLboolean green_enabled;
@@ -154,7 +165,12 @@ public:
static OpenGLState GetCurState() {
return cur_state;
}
static bool GetsRGBUsed() {
return s_rgb_used;
}
static void ClearsRGBUsed() {
s_rgb_used = false;
}
/// Apply this state as the current OpenGL state
void Apply() const;
@@ -169,6 +185,9 @@ public:
private:
static OpenGLState cur_state;
// Workaround for sRGB problems caused by
// QT not supporting srgb output
static bool s_rgb_used;
};
} // namespace OpenGL

View File

@@ -283,7 +283,8 @@ void RendererOpenGL::CreateRasterizer() {
if (rasterizer) {
return;
}
// Initialize sRGB Usage
OpenGLState::ClearsRGBUsed();
rasterizer = std::make_unique<RasterizerOpenGL>(render_window, screen_info);
}
@@ -356,13 +357,20 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
state.texture_units[0].texture = screen_info.display_texture;
state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
// Workaround brigthness problems in SMO by enabling sRGB in the final output
// if it has been used in the frame
// Needed because of this bug in QT
// QTBUG-50987
state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed();
state.Apply();
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// restore default state
state.framebuffer_srgb.enabled = false;
state.texture_units[0].texture = 0;
state.Apply();
// Clear sRGB state for the next frame
OpenGLState::ClearsRGBUsed();
}
/**

View File

@@ -142,7 +142,6 @@ void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
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;
@@ -320,13 +319,13 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
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_x = 64;
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_width = Common::AlignUp(width * bytes_per_pixel, 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;
return aligned_width * aligned_height * aligned_depth;
} else {
return width * height * depth * bytes_per_pixel;
}

View File

@@ -173,6 +173,7 @@ struct TICEntry {
};
union {
BitField<0, 16, u32> width_minus_1;
BitField<22, 1, u32> srgb_conversion;
BitField<23, 4, TextureType> texture_type;
};
union {
@@ -227,6 +228,10 @@ struct TICEntry {
return header_version == TICHeaderVersion::BlockLinear ||
header_version == TICHeaderVersion::BlockLinearColorKey;
}
bool IsSrgbConversionEnabled() const {
return srgb_conversion != 0;
}
};
static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");

View File

@@ -8,7 +8,6 @@
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/settings.h"
@@ -107,9 +106,8 @@ private:
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title));
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
setAttribute(Qt::WA_AcceptTouchEvents);
InputCommon::Init();

View File

@@ -13,11 +13,16 @@ Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
FileUtil::CreateFullPath(qt_config_loc);
qt_config = new QSettings(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload();
}
Config::~Config() {
Save();
}
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
@@ -342,9 +347,3 @@ void Config::Reload() {
void Config::Save() {
SaveValues();
}
Config::~Config() {
Save();
delete qt_config;
}

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <memory>
#include <string>
#include <QVariant>
#include "core/settings.h"
@@ -12,12 +13,6 @@
class QSettings;
class Config {
QSettings* qt_config;
std::string qt_config_loc;
void ReadValues();
void SaveValues();
public:
Config();
~Config();
@@ -27,4 +22,11 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
private:
void ReadValues();
void SaveValues();
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
};

View File

@@ -21,12 +21,8 @@
#include "yuzu/configuration/configure_system.h"
#include "yuzu/main.h"
static std::string GetImagePath(Service::Account::UUID uuid) {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
}
static const std::array<int, 12> days_in_month = {{
namespace {
constexpr std::array<int, 12> days_in_month = {{
31,
29,
31,
@@ -42,7 +38,7 @@ static const std::array<int, 12> days_in_month = {{
}};
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
static constexpr std::array<u8, 107> backup_jpeg{
constexpr std::array<u8, 107> backup_jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
@@ -52,15 +48,52 @@ static constexpr std::array<u8, 107> backup_jpeg{
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
QString GetImagePath(Service::Account::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QString GetAccountUsername(const Service::Account::ProfileManager& manager,
Service::Account::UUID uuid) {
Service::Account::ProfileBase profile;
if (!manager.GetProfileBase(uuid, profile)) {
return {};
}
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
return QString::fromStdString(text);
}
QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
return ConfigureSystem::tr("%1\n%2",
"%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
QPixmap GetIcon(Service::Account::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), backup_jpeg.size());
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
} // Anonymous namespace
ConfigureSystem::ConfigureSystem(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureSystem),
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&ConfigureSystem::updateBirthdayComboBox);
&ConfigureSystem::UpdateBirthdayComboBox);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::refreshConsoleID);
&ConfigureSystem::RefreshConsoleID);
layout = new QVBoxLayout;
tree_view = new QTreeView;
@@ -118,18 +151,6 @@ void ConfigureSystem::setConfiguration() {
UpdateCurrentUser();
}
static QPixmap GetIcon(Service::Account::UUID uuid) {
const auto icon_url = QString::fromStdString(GetImagePath(uuid));
QPixmap icon{icon_url};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), backup_jpeg.size());
}
return icon;
}
void ConfigureSystem::PopulateUserList() {
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
@@ -141,8 +162,7 @@ void ConfigureSystem::PopulateUserList() {
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
QString::fromStdString(username + '\n' + user.FormatSwitch())}});
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
@@ -153,25 +173,17 @@ void ConfigureSystem::UpdateCurrentUser() {
ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
ASSERT(current_user != boost::none);
const auto username = GetAccountUsername(*current_user);
ASSERT(current_user != std::nullopt);
const auto username = GetAccountUsername(*profile_manager, *current_user);
scene->clear();
scene->addPixmap(
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
ui->current_user_username->setText(QString::fromStdString(username));
ui->current_user_username->setText(username);
}
void ConfigureSystem::ReadSystemSettings() {}
std::string ConfigureSystem::GetAccountUsername(Service::Account::UUID uuid) const {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(uuid, profile))
return "";
return Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
}
void ConfigureSystem::applyConfiguration() {
if (!enabled)
return;
@@ -180,7 +192,7 @@ void ConfigureSystem::applyConfiguration() {
Settings::Apply();
}
void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
if (birthmonth_index < 0 || birthmonth_index >= 12)
return;
@@ -205,7 +217,7 @@ void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
ui->combo_birthday->setCurrentIndex(birthday_index);
}
void ConfigureSystem::refreshConsoleID() {
void ConfigureSystem::RefreshConsoleID() {
QMessageBox::StandardButton reply;
QString warning_text = tr("This will replace your current virtual Switch with a new one. "
"Your current virtual Switch will not be recoverable. "
@@ -232,8 +244,7 @@ void ConfigureSystem::SelectUser(const QModelIndex& index) {
}
void ConfigureSystem::AddUser() {
Service::Account::UUID uuid;
uuid.Generate();
const auto uuid = Service::Account::UUID::Generate();
bool ok = false;
const auto username =
@@ -244,25 +255,23 @@ void ConfigureSystem::AddUser() {
profile_manager->CreateNewUser(uuid, username.toStdString());
item_model->appendRow(new QStandardItem{
GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
QString::fromStdString(username.toStdString() + '\n' + uuid.FormatSwitch())});
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
}
void ConfigureSystem::RenameUser() {
const auto user = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(user);
ASSERT(uuid != boost::none);
const auto username = GetAccountUsername(*uuid);
ASSERT(uuid != std::nullopt);
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(*uuid, profile))
return;
bool ok = false;
const auto old_username = GetAccountUsername(*profile_manager, *uuid);
const auto new_username =
QInputDialog::getText(this, tr("Enter Username"), tr("Enter a new username:"),
QLineEdit::Normal, QString::fromStdString(username), &ok);
QLineEdit::Normal, old_username, &ok);
if (!ok)
return;
@@ -280,25 +289,20 @@ void ConfigureSystem::RenameUser() {
item_model->setItem(
user, 0,
new QStandardItem{
GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
tr("%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(QString::fromStdString(username_std),
QString::fromStdString(uuid->FormatSwitch()))});
new QStandardItem{GetIcon(*uuid),
FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
UpdateCurrentUser();
}
void ConfigureSystem::DeleteUser() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid != boost::none);
const auto username = GetAccountUsername(*uuid);
ASSERT(uuid != std::nullopt);
const auto username = GetAccountUsername(*profile_manager, *uuid);
const auto confirm =
QMessageBox::question(this, tr("Confirm Delete"),
tr("You are about to delete user with name %1. Are you sure?")
.arg(QString::fromStdString(username)));
const auto confirm = QMessageBox::question(
this, tr("Confirm Delete"),
tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
if (confirm == QMessageBox::No)
return;
@@ -320,29 +324,48 @@ void ConfigureSystem::DeleteUser() {
void ConfigureSystem::SetUserImage() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid != boost::none);
const auto username = GetAccountUsername(*uuid);
ASSERT(uuid != std::nullopt);
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
"JPEG Images (*.jpg *.jpeg)");
tr("JPEG Images (*.jpg *.jpeg)"));
if (file.isEmpty())
if (file.isEmpty()) {
return;
}
FileUtil::Delete(GetImagePath(*uuid));
const auto image_path = GetImagePath(*uuid);
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
QMessageBox::warning(
this, tr("Error deleting image"),
tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
return;
}
const auto raw_path =
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
FileUtil::Delete(raw_path);
const auto raw_path = QString::fromStdString(
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
tr("Unable to delete existing file: %1.").arg(raw_path));
return;
}
FileUtil::CreateFullPath(GetImagePath(*uuid));
FileUtil::Copy(file.toStdString(), GetImagePath(*uuid));
const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
QMessageBox::warning(
this, tr("Error creating user image directory"),
tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
return;
}
item_model->setItem(
index, 0,
new QStandardItem{
GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
QString::fromStdString(username + '\n' + uuid->FormatSwitch())});
if (!QFile::copy(file, image_path)) {
QMessageBox::warning(this, tr("Error copying user image"),
tr("Unable to copy image from %1 to %2").arg(file, image_path));
return;
}
const auto username = GetAccountUsername(*profile_manager, *uuid);
item_model->setItem(index, 0,
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
UpdateCurrentUser();
}

View File

@@ -9,17 +9,16 @@
#include <QList>
#include <QWidget>
namespace Service::Account {
class ProfileManager;
struct UUID;
} // namespace Service::Account
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Service::Account {
class ProfileManager;
}
namespace Ui {
class ConfigureSystem;
}
@@ -29,28 +28,25 @@ class ConfigureSystem : public QWidget {
public:
explicit ConfigureSystem(QWidget* parent = nullptr);
~ConfigureSystem();
~ConfigureSystem() override;
void applyConfiguration();
void setConfiguration();
private:
void ReadSystemSettings();
void UpdateBirthdayComboBox(int birthmonth_index);
void RefreshConsoleID();
void PopulateUserList();
void UpdateCurrentUser();
public slots:
void updateBirthdayComboBox(int birthmonth_index);
void refreshConsoleID();
void SelectUser(const QModelIndex& index);
void AddUser();
void RenameUser();
void DeleteUser();
void SetUserImage();
private:
void ReadSystemSettings();
std::string GetAccountUsername(Service::Account::UUID uuid) const;
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
@@ -59,11 +55,12 @@ private:
std::vector<QList<QStandardItem*>> list_items;
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled;
bool enabled = false;
int birthmonth, birthday;
int language_index;
int sound_index;
int birthmonth = 0;
int birthday = 0;
int language_index = 0;
int sound_index = 0;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <map>
#include <QLabel>
#include <QMetaType>
#include <QPushButton>
@@ -32,21 +31,8 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
switch (role) {
case Qt::DisplayRole: {
if (index.column() == 0) {
static const std::map<Tegra::DebugContext::Event, QString> map = {
{Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
{Tegra::DebugContext::Event::MaxwellCommandProcessed,
tr("Maxwell command processed")},
{Tegra::DebugContext::Event::IncomingPrimitiveBatch,
tr("Incoming primitive batch")},
{Tegra::DebugContext::Event::FinishedPrimitiveBatch,
tr("Finished primitive batch")},
};
DEBUG_ASSERT(map.size() ==
static_cast<std::size_t>(Tegra::DebugContext::Event::NumEvents));
return (map.find(event) != map.end()) ? map.at(event) : QString();
return DebugContextEventToString(event);
}
break;
}
@@ -128,6 +114,23 @@ void BreakPointModel::OnResumed() {
active_breakpoint = context->active_breakpoint;
}
QString BreakPointModel::DebugContextEventToString(Tegra::DebugContext::Event event) {
switch (event) {
case Tegra::DebugContext::Event::MaxwellCommandLoaded:
return tr("Maxwell command loaded");
case Tegra::DebugContext::Event::MaxwellCommandProcessed:
return tr("Maxwell command processed");
case Tegra::DebugContext::Event::IncomingPrimitiveBatch:
return tr("Incoming primitive batch");
case Tegra::DebugContext::Event::FinishedPrimitiveBatch:
return tr("Finished primitive batch");
case Tegra::DebugContext::Event::NumEvents:
break;
}
return tr("Unknown debug context event");
}
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(

View File

@@ -29,6 +29,8 @@ public:
void OnResumed();
private:
static QString DebugContextEventToString(Tegra::DebugContext::Event event);
std::weak_ptr<Tegra::DebugContext> context_weak;
bool at_breakpoint;
Tegra::DebugContext::Event active_breakpoint;

View File

@@ -16,7 +16,6 @@
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/patch_manager.h"
#include "yuzu/compatibility_list.h"
@@ -217,11 +216,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
item_model->insertColumns(0, COLUMN_COUNT);
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons");
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
@@ -387,9 +386,9 @@ void GameList::LoadCompatibilityList() {
}
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
if (!FileUtil::Exists(dir_path.toStdString()) ||
!FileUtil::IsDirectory(dir_path.toStdString())) {
LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toLocal8Bit().data());
const QFileInfo dir_info{dir_path};
if (!dir_info.exists() || !dir_info.isDir()) {
LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
search_field->setFilterResult(0, 0);
return;
}

View File

@@ -62,19 +62,24 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
FileSys::VirtualFile update_raw;
loader.ReadUpdateRaw(update_raw);
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
if (!updatable && kv.first == "Update")
const bool is_update = kv.first == "Update";
if (!updatable && is_update) {
continue;
}
const QString type = QString::fromStdString(kv.first);
if (kv.second.empty()) {
out.append(fmt::format("{}\n", kv.first).c_str());
out.append(QStringLiteral("%1\n").arg(type));
} else {
auto ver = kv.second;
// Display container name for packed updates
if (ver == "PACKED" && kv.first == "Update")
if (is_update && ver == "PACKED") {
ver = Loader::GetFileTypeString(loader.GetFileType());
}
out.append(fmt::format("{} ({})\n", kv.first, ver).c_str());
out.append(QStringLiteral("%1 (%2)\n").arg(type, QString::fromStdString(ver)));
}
}

View File

@@ -30,6 +30,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QtConcurrent/QtConcurrent>
@@ -785,7 +786,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
ASSERT(index != -1 && index < 8);
const auto user_id = manager.GetUser(index);
ASSERT(user_id != boost::none);
ASSERT(user_id != std::nullopt);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData,
program_id, user_id->uuid, 0);
@@ -1336,20 +1337,40 @@ void GMainWindow::OnLoadAmiibo() {
const QString extensions{"*.bin"};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
if (!filename.isEmpty()) {
Core::System& system{Core::System::GetInstance()};
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
if (nfc != nullptr) {
auto nfc_file = FileUtil::IOFile(filename.toStdString(), "rb");
if (!nfc_file.IsOpen()) {
return;
}
std::vector<u8> amiibo_buffer(nfc_file.GetSize());
nfc_file.ReadBytes(amiibo_buffer.data(), amiibo_buffer.size());
nfc_file.Close();
nfc->LoadAmiibo(amiibo_buffer);
}
if (filename.isEmpty()) {
return;
}
Core::System& system{Core::System::GetInstance()};
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
if (nfc == nullptr) {
return;
}
QFile nfc_file{filename};
if (!nfc_file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Error opening Amiibo data file"),
tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
return;
}
const u64 nfc_file_size = nfc_file.size();
std::vector<u8> buffer(nfc_file_size);
const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
if (nfc_file_size != read_size) {
QMessageBox::warning(this, tr("Error reading Amiibo data file"),
tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
"was only able to read %2 bytes.")
.arg(nfc_file_size)
.arg(read_size));
return;
}
if (!nfc->LoadAmiibo(buffer)) {
QMessageBox::warning(this, tr("Error loading Amiibo data"),
tr("Unable to load Amiibo data."));
}
}