Compare commits

..

110 Commits

Author SHA1 Message Date
FernandoS27
be97fc884d Implement Scissor Test 2018-10-08 21:36:23 -04:00
FernandoS27
30ff42b8cc Assert Scissor tests 2018-10-08 20:49:36 -04:00
bunnei
561d79e034 Merge pull request #1423 from DarkLordZach/romfs-file-exts
fsmitm_romfsbuild: Add support for stubbing and IPS patches in LFS
2018-10-08 12:31:27 -04:00
bunnei
6b48ba5271 Merge pull request #1424 from DarkLordZach/ips-witch
ips_layer: Add support for IPSwitch executable patches
2018-10-08 12:30:33 -04:00
bunnei
fd891ee9c0 Merge pull request #1456 from ogniK5377/aoc-u-fixups
Fixed assertion due to CountAddOnContent & Casting warnings
2018-10-08 01:21:05 -04:00
bunnei
3f1f82a8c4 Merge pull request #1457 from ogniK5377/unmap-buffer
Unmapping an unmapped buffer should succeed
2018-10-08 01:20:18 -04:00
bunnei
ae982a9bdf Merge pull request #1419 from DarkLordZach/homebrew-args
nso/nro: Add support for passing command-line arguments to executable
2018-10-08 01:19:39 -04:00
David Marcec
c5c184246d Unmapping an unmapped buffer should succeed
Hardware tests show that trying to unmap an unmapped buffer already should always succeed. Hardware test was tested up to 32 iterations of attempting to unmap
2018-10-08 13:26:48 +11:00
Zach Hilman
f945e9767c nso/nro: Use default allocation size for arg_data 2018-10-07 14:32:33 -04:00
Zach Hilman
081f5c1dbf cmd: Support passing game arguments from command line
Uses -p (--program) and following string as args.
2018-10-07 14:32:32 -04:00
Zach Hilman
8bbc12b9c2 qt: Add UI option to configure arguments 2018-10-07 14:32:06 -04:00
Zach Hilman
95dff555a4 settings: Add program_args string setting 2018-10-07 14:32:05 -04:00
Zach Hilman
e09505ff61 nso/nro: Add NSO arguments structure to data section
Only added if arguments string is non-empty and a pass is requested by loader.
2018-10-07 14:30:15 -04:00
David Marcec
fa3f3cd07f Fixed assertion due to CountAddOnContent
Word count should be 3 since we're pushing a result code and a u32.

Also fixed up compiler warnings due to casting
2018-10-08 00:25:46 +11:00
bunnei
6e4d2e672d Merge pull request #1396 from DarkLordZach/packed-updates
loader: Add support for packed updates
2018-10-06 23:58:24 -04:00
bunnei
2c0b0ad50d Merge pull request #1446 from bunnei/fast_fermi_copy
gl_rasterizer: Implement accelerated Fermi2D copies.
2018-10-06 23:18:52 -04:00
bunnei
1cc5e6e9bc Merge pull request #1437 from FernandoS27/tex-mode2
Implemented Depth Compare, Shadow Samplers and Texture Processing Modes for TEXS and TLDS
2018-10-06 23:14:27 -04:00
bunnei
6f420a40cf Merge pull request #1453 from FearlessTobi/port-4311
Port citra-emu/citra#4311: "Remove "#" in the version number"
2018-10-06 23:12:58 -04:00
bunnei
44a3baf410 Merge pull request #1451 from FearlessTobi/port-4140
Port citra-emu/citra#4140: "misc input tab improvements"
2018-10-06 23:11:32 -04:00
Mat M
e92251795a Merge pull request #1450 from FearlessTobi/port-4312
Port citra-emu/citra#4312: "Update fmt to 5.2.1"
2018-10-06 22:26:06 -04:00
bunnei
450c0a5adf Merge pull request #1448 from ogniK5377/frontend-access
Ported #4296 from citra
2018-10-06 22:25:29 -04:00
Mat M
72a804b149 Merge pull request #1454 from ReinUsesLisp/fixup-draw
gl_rasterizer: Fixup undefined behaviour in SetupDraw
2018-10-06 22:24:08 -04:00
ReinUsesLisp
0ecd181cca gl_rasterizer: Fixup undefined behaviour in SetupDraw 2018-10-06 23:22:48 -03:00
bunnei
2fbb20b2b5 yuzu/yuzu_cmd: Add checks for required extension ARB_copy_image. 2018-10-06 12:06:40 -04:00
FernandoS27
752faff2bc Implemented Depth Compare and Shadow Samplers 2018-10-06 11:27:54 -04:00
Mat M
6d4f5b9673 Merge pull request #1452 from FearlessTobi/port-4313
Port citra-emu/citra#4313: "CONTRIBUTING.md - remove note about casting numeric types"
2018-10-06 10:11:35 -04:00
fearlessTobi
8e6311bfd2 Remove "#" in the version number
So that people can stop using it in issue/pr comments and randomly link some other issue/pr unintentionally.
2018-10-06 15:51:37 +02:00
NeatNit
d210170f36 CONTRIBUTING.md - remove note about casting numeric types
Apparently it's not true: https://github.com/citra-emu/citra/pull/4310#discussion_r223174725
2018-10-06 15:47:38 +02:00
zhupengfei
690f326613 citra_qt/configuration: misc input tab improvements
* Added a context menu on the buttons including Clear & Restore Default

* Allow clearing (unsetting) inputs. Added a Clear All button

* Allow restoring a single input to default (instead of all)
2018-10-06 15:43:49 +02:00
Weiyi Wang
a45b86f0fb Update fmt to 5.2.1
It seems to fix msvc warnings
2018-10-06 15:39:31 +02:00
David Marcec
612ce89eca Added forward define for ServerPort 2018-10-06 17:47:33 +10:00
bunnei
9aec85d39c fermi_2d: Implement simple copies with AccelerateSurfaceCopy. 2018-10-06 03:20:04 -04:00
bunnei
011cf77796 gl_rasterizer: Add rasterizer cache code to handle accerated fermi copies. 2018-10-06 03:20:04 -04:00
bunnei
749aef3dd0 gl_rasterizer_cache: Implement a simpler surface copy using glCopyImageSubData. 2018-10-06 03:20:04 -04:00
bunnei
6cc7656e81 Merge pull request #1449 from lioncash/link
qt: Update telemetry links
2018-10-06 03:18:13 -04:00
Lioncash
efd956e6ff qt: Update telemetry links
These were pointing to a non-existent webpage.
2018-10-06 03:16:39 -04:00
David Marcec
f84b9ed4e8 Ported #4296 from citra
This will allow us to easily remove the use of "NFC" in "System"
2018-10-06 16:49:01 +10:00
bunnei
b8b90ce6e6 Merge pull request #1332 from FearlessTobi/port-web-backend
Port web_service from Citra
2018-10-06 02:43:09 -04:00
bunnei
095c8d999b Merge pull request #1447 from lioncash/mutex
kernel/mutex: Amend behavior of TransferMutexOwnership()
2018-10-06 01:38:34 -04:00
Lioncash
6ddf8f34db kernel/mutex: Amend behavior of TransferMutexOwnership()
This was the result of a typo accidentally introduced in
e51d715700. This restores the previous
correct behavior.

The behavior with the reference was incorrect and would cause some games
to fail to boot.
2018-10-06 01:13:02 -04:00
bunnei
e6ee31a8e9 Merge pull request #1440 from lioncash/array
ui_settings: Place definition of the theme array within the cpp file
2018-10-05 22:54:01 -04:00
bunnei
d3bfb102d8 Merge pull request #1438 from ReinUsesLisp/quads
gl_rasterizer: Implement quads topology
2018-10-05 22:53:22 -04:00
bunnei
d43769f93f Merge pull request #1445 from lioncash/sched
thread: Make the scheduler pointer a regular pointer
2018-10-05 22:33:03 -04:00
balika011
1a5d6de0d4 thread: Make the scheduler pointer a regular pointer
Conceptually, it doesn't make sense for a thread to be able to persist
the lifetime of a scheduler. A scheduler should be taking care of the
threads; the threads should not be taking care of the scheduler.

If the threads outlive the scheduler (or we simply don't actually
terminate/shutdown the threads), then it should be considered a bug
that we need to fix.

Attributing this to balika011, as they opened #1317 to attempt to fix
this in a similar way, but my refactoring of the kernel code caused
quite a few conflicts.
2018-10-05 14:53:01 -04:00
bunnei
e51d715700 Merge pull request #1439 from lioncash/thread
kernel/thread: Make all instance variables private
2018-10-05 13:41:54 -04:00
bunnei
fe292573de Merge pull request #1442 from lioncash/format
text_formatter: Avoid unnecessary string temporary creation in PrintMessage()
2018-10-05 10:48:58 -04:00
Zach Hilman
38c2ac95af romfs_factory: Extract packed update setter to new function 2018-10-05 08:53:51 -04:00
Zach Hilman
5acaeb04c4 patch_manager: Add support for NSP packed updates
Reads as Update (NSP) in add-ons
2018-10-05 08:48:44 -04:00
Zach Hilman
cf7aba4817 game_list: Add XCI update versioning to game list 2018-10-05 08:47:55 -04:00
Zach Hilman
d79d4fd764 patch_manager: Add support for packed updates
Will prefer any installed update over the packed version.
2018-10-05 08:47:24 -04:00
Zach Hilman
5045748829 loader: Add getter for packed update
Reads the update included with the game if it has one and adds the new ErrorNoPackedUpdate status.
2018-10-05 08:46:31 -04:00
Zach Hilman
e948fbf5d0 loader: Add ReadRomFSIVFCOffset to NSP, XCI, and NAX loaders
Fixes errors with certain updates
2018-10-05 08:46:31 -04:00
Lioncash
6f16826260 text_formatter: Avoid unnecessary string temporary creation in PrintMessage()
operator+ for std::string creates an entirely new string, which is kind
of unnecessary here if we just want to append a null terminator to the
existing one.

Reduces the total amount of potential allocations that need to be done
in the logging path.
2018-10-04 23:55:53 -04:00
Zach Hilman
d0e6b93695 patch_manager: Avoid romfs_ext requirement for patching 2018-10-04 14:09:11 -04:00
Zach Hilman
c1e069c066 fsmitm_romfsbuild: Extract stubs and IPS to romfs_ext dir 2018-10-04 12:29:20 -04:00
Zach Hilman
bc4bec8a60 fsmitm_romfsbuild: Add support for stubbing and IPS patches in LFS 2018-10-04 12:29:14 -04:00
Zach Hilman
110d578470 ips_layer: Fix inaccuracies with comments and flags
Specifically bugs/crashes that arise when putting them in positions that are legal but not typical, such as midline, between patch data, or between patch records.
2018-10-04 12:23:27 -04:00
Zach Hilman
70bd2bb1d3 ips_layer: Deduplicate resource usage 2018-10-04 11:34:36 -04:00
Zach Hilman
9669cdb710 ips_layer: Add support for escape sequences and midline comments
More accurately follows IPSwitch specification.
2018-10-04 11:34:30 -04:00
Zach Hilman
8886f2e55e patch_manager: Add support for IPSwitch format patches 2018-10-04 11:34:06 -04:00
Zach Hilman
306739c2c4 ips_layer: Add IPSwitchCompiler to process IPSwitch format 2018-10-04 11:32:10 -04:00
Zach Hilman
f62227aa95 hex_util: Add HexVectorToString and HexStringToVector
Converts between bytes and strings when the size is not known at compile time.
2018-10-04 11:32:04 -04:00
Lioncash
30dfd89126 ui_settings: Place definition of the theme array within the cpp file
Placing the array wholesale into the header places a copy of the whole
array into every translation unit that uses the data, which is wasteful.
Particularly given that this array is referenced from three different
translation units.

This also changes the array to contain pairs of const char*, rather than
QString instances. This way, the string data is able to be fixed into
the read-only segment of the program, as well as eliminate static
constructors/heap allocation immediately on program start.
2018-10-04 09:43:51 -04:00
bunnei
f85f2b3728 Merge pull request #1415 from DarkLordZach/ips
file_sys: Add support for loading IPS patches
2018-10-04 09:42:37 -04:00
Lioncash
baed7e1fba kernel/thread: Make all instance variables private
Many of the member variables of the thread class aren't even used
outside of the class itself, so there's no need to make those variables
public. This change follows in the steps of the previous changes that
made other kernel types' members private.

The main motivation behind this is that the Thread class will likely
change in the future as emulation becomes more accurate, and letting
random bits of the emulator access data members of the Thread class
directly makes it a pain to shuffle around and/or modify internals.
Having all data members public like this also makes it difficult to
reason about certain bits of behavior without first verifying what parts
of the core actually use them.

Everything being public also generally follows the tendency for changes
to be introduced in completely different translation units that would
otherwise be better introduced as an addition to the Thread class'
public interface.
2018-10-04 00:14:15 -04:00
ReinUsesLisp
3e2380327a gl_rasterizer: Implement quads topology 2018-10-04 00:03:44 -03:00
bunnei
cf3a6dd4a1 Merge pull request #1434 from DarkLordZach/dlc-edge-case
aoc_u: Fix edge case with DLC that causes breaks
2018-10-03 21:39:23 -04:00
bunnei
af672d8abf Merge pull request #1428 from lioncash/qt
configure_graphics: Make functions internally linked where applicable
2018-10-03 21:38:29 -04:00
bunnei
839dbd9710 Merge pull request #1431 from lioncash/audio
configure_audio: Minor cleanup-related changes
2018-10-03 19:14:22 -04:00
bunnei
39f08e551d Merge pull request #1433 from lioncash/fs
services/fsp_srv: Amend service function table
2018-10-03 19:13:57 -04:00
bunnei
5ae5610c13 Merge pull request #1429 from lioncash/translate
configure_input: Make analog mapping strings translatable
2018-10-03 19:13:20 -04:00
bunnei
15b2e2ec13 Merge pull request #1436 from lioncash/view
submission_package: Cleanup and bug fixes
2018-10-03 19:05:19 -04:00
bunnei
e7e347a828 Merge pull request #1432 from lioncash/lbl
service/lbl: Update service function table
2018-10-03 18:45:15 -04:00
bunnei
137d43fa2f Merge pull request #1426 from FearlessTobi/port-4253
Port citra-emu/citra#4253: "common/string_util cleanup"
2018-10-03 18:44:51 -04:00
bunnei
8679934693 Merge pull request #1435 from lioncash/xci
card_image: Ensure program_nca_status is always initialized
2018-10-03 18:44:28 -04:00
FernandoS27
f664437ae8 Implemented Texture Processing Modes in TEXS and TLDS 2018-10-03 08:41:12 -04:00
Lioncash
024eec02a5 submission_package: Avoid dangling std::string_view within SetTicketKeys()
GetName() returns a std::string by value, not by reference, so after the
std::string_view is constructed, it's not well defined to actually
execute any member functions of std::string_view that attempt to access
the data, as the std::string has already been destroyed. Instead, we can
just use a std::string and erase the last four characters.
2018-10-03 02:13:51 -04:00
Lioncash
dade709f63 submission_package: Correct location of null check within SetTicketKeys()
If a ticket file was ever a null pointer, we'd cause a null pointer
dereference, as we were calling GetExtension() on the pointer instance.
2018-10-03 02:06:30 -04:00
Lioncash
02841052aa submission_package: Use std::string's rfind() when looking for the extension in InitializeExeFSAndRomFS()
When searching for a file extension, it's generally preferable to begin
the search at the end of the string rather than the beginning, as the
whole string isn't going to be walked just to check for something at the
end of it.
2018-10-03 01:51:01 -04:00
Lioncash
37ee05f7c0 submission_package: Ensure the 'extracted' member variable is always initialized
If an error occurs when constructing the PartitionFilesystem instance,
the constructor would be exited early, which wouldn't initialize the
extracted data member, making it possible for other code to perform an
uninitialized read by calling the public IsExtractedType() member
function. This prevents that.
2018-10-03 01:47:34 -04:00
Lioncash
ccf0a9cb38 submission_package: Move ExeFS and RomFS initialization to its own function
Like the other two bits of factored out code, this can also be put
within its own function. We can also modify the code so that it accepts
a const reference to a std::vector of files, this way, we can
deduplicate the file retrieval.

Now the constructor for NSP isn't a combination of multiple behaviors in
one spot. It's nice and separate.
2018-10-03 01:46:07 -04:00
Lioncash
fd312abedd submission_package: Move NCA reading code to its own function
This too, is completely separate behavior from what is in the
constructor, so we can move this to its own isolated function to keep
everything self-contained.
2018-10-03 01:35:41 -04:00
Lioncash
4f18d35888 submission_package: Move ticket key setting to its own function
This behavior is entirely independent of the surrounding code, so it can
be put in its own function to keep the behavior separate.
2018-10-03 01:20:57 -04:00
Lioncash
0d83f8f255 submission_package: Invert conditionals within NSP's constructor to reduce nesting
We can use early continues here to reduce the amount of nesting.
2018-10-03 01:11:16 -04:00
Lioncash
cf463a9b67 card_image: Ensure program_nca_status is always initialized
If any of the error paths before the NCA retrieval are taken, it'll
result in program_nca_status being left in an inconsistent state. So we
initialize it by default with a value indicating an error.
2018-10-02 23:16:05 -04:00
Lioncash
cba78dc70a services/fsp_srv: Amend service function table
Adds new functions that have been given names to the table. Information
is based off what is provided on Switchbrew.
2018-10-02 21:34:33 -04:00
Lioncash
d80dd2b812 service/lbl: Update service function table
Amends the lbl service table to include new names of functions that were
added to Switchbrew.
2018-10-02 21:15:59 -04:00
Lioncash
61b144bbf3 configure_input: Make analog mapping strings translatable
These strings are user-facing, so they should be specified as
translatable with tr().
2018-10-02 20:28:45 -04:00
Lioncash
1c7c798e9e configure_audio: Move combo box index setting to their own functions
Keeps the individual behaviors in their own functions, and cleanly
separate. We can also do a little better by converting the relevant IDs
within the core to a QString only once, instead of converting every
string into a std::string.
2018-10-02 20:24:30 -04:00
Lioncash
57e47bae32 configure_audio: Use QString::fromStdString() for converting audio device names
This ensures that the proper codec will always be used no matter what.
It also avoids relying on ASCII conversions.
2018-10-02 19:50:51 -04:00
Lioncash
226dc914b3 configure_audio: Add disambiguation comment for the volume percentage string
Disambiguates what the string represents to help translators more easily
understand what it is that they're translating. While we're at it, we
can move the code to its own function, so that we don't need to specify
the same string twice.
2018-10-02 19:45:12 -04:00
Lioncash
8f5e2a2b83 configure_graphics: Make functions internally linked where applicable
These aren't used outside of this translation unit, so they can be
internally linked.
2018-10-02 19:24:43 -04:00
Zach Hilman
215b65fe75 nso: Optimize loading of IPS patches
Avoid resource-heavy classes and remove quasi-duplicated code.
2018-10-02 17:03:38 -04:00
Weiyi Wang
fdb35760a7 string_util: unify UTF8<->UTF16 conversion to codecvt 2018-10-02 16:10:34 +02:00
Weiyi Wang
bfe84f06f2 string_util: remove TString conversion for windows
First of all they are foundamentally broken. As our convention is that std::string is always UTF-8, these functions assume that the multi-byte character version of TString (std::string) from windows is also in UTF-8, which is almost always wrong. We are not going to build multi-byte character build, and even if we do, this dirty work should be handled by frontend framework early.
2018-10-02 16:10:20 +02:00
Weiyi Wang
a4595bb939 string_util: remove ShiftJIS/CP1252 conversion function
We always use unicode internally. Any dirty work of conversion with other codec should be handled by frontend framework (Qt). Further more, ShiftJIS/CP1252 are not special (they are not code set used by 3ds, or any guest/host dependencies we have), so there is no reason to specifically include them
2018-10-02 16:10:01 +02:00
fearlessTobi
e4daf4bee5 Review comments - part 5 2018-10-02 16:04:10 +02:00
fearlessTobi
ac06105dfe Review comments -part 4 2018-10-02 15:30:49 +02:00
fearlessTobi
aa48468862 Review comments - part 3 2018-10-02 15:30:48 +02:00
Weiyi Wang
62f9409ba3 web_backend: protect jwt cache with a mutex 2018-10-02 15:30:48 +02:00
fearlessTobi
120d8f3bf7 Address more review comments 2018-10-02 15:30:48 +02:00
fearlessTobi
b4ace6ec6f Address a bunch of review comments 2018-10-02 15:30:48 +02:00
fearlessTobi
4d139943f2 Port web_service from Citra 2018-10-02 15:30:48 +02:00
fearlessTobi
5f30f95e94 Add submodules 2018-10-02 15:30:48 +02:00
Zach Hilman
6d441828e6 deconstructed_rom_directory: Force NSO loader to patch NSOs 2018-10-01 16:02:50 -04:00
Zach Hilman
003b44822a nso: Add framework to support patching of uncompressed NSOs 2018-10-01 16:02:50 -04:00
Zach Hilman
42fb4e82d3 patch_manager: Add PatchNSO function
While PatchExeFS operated on the entire directory, this function operates on the uncompressed NSO. Avoids copying decompression code to PatchManager.
2018-10-01 16:02:50 -04:00
Zach Hilman
4c2a94fa94 patch_manager: Use strings for patch type instead of enum 2018-10-01 16:02:50 -04:00
Zach Hilman
21b2411c44 file_sys: Implement function to apply IPS patches 2018-10-01 16:01:46 -04:00
Zach Hilman
3c9e70fefa nso: Replace NSOHeader padding bytes with build ID 2018-10-01 16:01:46 -04:00
145 changed files with 23900 additions and 831 deletions

6
.gitmodules vendored
View File

@@ -34,3 +34,9 @@
[submodule "soundtouch"]
path = externals/soundtouch
url = https://github.com/citra-emu/ext-soundtouch.git
[submodule "libressl"]
path = externals/libressl
url = https://github.com/citra-emu/ext-libressl-portable.git
[submodule "discord-rpc"]
path = externals/discord-rpc
url = https://github.com/discordapp/discord-rpc.git

View File

@@ -10,3 +10,7 @@ TRAVIS_JOB_ID
TRAVIS_JOB_NUMBER
TRAVIS_REPO_SLUG
TRAVIS_TAG
# yuzu specific flags
ENABLE_COMPATIBILITY_REPORTING
USE_DISCORD_PRESENCE

View File

@@ -1,4 +1,4 @@
#!/bin/bash -ex
mkdir -p "$HOME/.ccache"
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh

View File

@@ -6,7 +6,7 @@ apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev
cd /yuzu
mkdir build && cd build
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -G Ninja
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
ninja
ccache -s

View File

@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
mkdir build && cd build
cmake --version
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
make -j4
ccache -s

View File

@@ -15,10 +15,14 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON
option(ENABLE_QT "Enable the Qt frontend" ON)
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@@ -344,14 +348,6 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
ENDIF (APPLE)
# MINGW: GCC does not support codecvt, so use iconv instead
if (UNIX OR MINGW)
find_library(ICONV_LIBRARY NAMES iconv)
if (ICONV_LIBRARY)
list(APPEND PLATFORM_LIBRARIES ${ICONV_LIBRARY})
endif()
endif()
# Setup a custom clang-format target (if clang-format can be found) that will run
# against all the src files. This should be used before making a pull request.
# =======================================================================

View File

@@ -22,7 +22,7 @@ If clang format is found, then cmake will add a custom build target that can be
* Don't ever introduce new external dependencies into Core
* Don't use any platform specific code in Core
* Use namespaces often
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`. The only exception to this rule is for casting between two numeric types, where C-style casts are encouraged for brevity and readability.
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`.
### Naming Rules
* Functions: `PascalCase`

View File

@@ -39,11 +39,12 @@ before_build:
- mkdir %BUILD_TYPE%_build
- cd %BUILD_TYPE%_build
- ps: |
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
if ($env:BUILD_TYPE -eq 'msvc') {
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0'
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
} else {
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1"
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
}
- cd ..

View File

@@ -70,3 +70,28 @@ if(ENABLE_CUBEB)
set(BUILD_TESTS OFF CACHE BOOL "")
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
endif()
# DiscordRPC
if (USE_DISCORD_PRESENCE)
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()
if (ENABLE_WEB_SERVICE)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
# lurlparser
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
# JSON
add_library(json-headers INTERFACE)
target_include_directories(json-headers INTERFACE ./json)
endif()

1
externals/discord-rpc vendored Submodule

Submodule externals/discord-rpc added at e32d001809

2
externals/fmt vendored

15
externals/httplib/README.md vendored Normal file
View File

@@ -0,0 +1,15 @@
From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3
MIT License
===
cpp-httplib
A C++11 header-only HTTP library.
It's extremely easy to setup. Just include httplib.h file in your code!
Inspired by Sinatra and express.
© 2017 Yuji Hirose

2344
externals/httplib/httplib.h vendored Normal file

File diff suppressed because it is too large Load Diff

9
externals/json/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
JSON for Modern C++
===================
v3.1.2
This is a mirror providing the single required header file.
The original repository can be found at:
https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe

17300
externals/json/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

1
externals/libressl vendored Submodule

Submodule externals/libressl added at 7d01cb01cb

8
externals/lurlparser/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,8 @@
add_library(lurlparser
LUrlParser.cpp
LUrlParser.h
)
create_target_directory_groups(lurlparser)
target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

265
externals/lurlparser/LUrlParser.cpp vendored Normal file
View File

@@ -0,0 +1,265 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "LUrlParser.h"
#include <algorithm>
#include <cstring>
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid( const std::string& SchemeName )
{
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
}
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if ( !IsValid() ) { return false; }
int Port = atoi( m_Port.c_str() );
if ( Port <= 0 || Port > 65535 ) { return false; }
if ( OutPort ) { *OutPort = Port; }
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}

78
externals/lurlparser/LUrlParser.h vendored Normal file
View File

@@ -0,0 +1,78 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
namespace LUrlParser
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode( LUrlParserError_Uninitialized )
{}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
bool GetPort( int* OutPort ) const;
/// parse the URL
static clParseURL ParseURL( const std::string& URL );
private:
explicit clParseURL( LUrlParserError ErrorCode )
: m_ErrorCode( ErrorCode )
{}
};
} // namespace LUrlParser

19
externals/lurlparser/README.md vendored Normal file
View File

@@ -0,0 +1,19 @@
From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
MIT License
===
Lightweight URL & URI parser (RFC 1738, RFC 3986)
(C) Sergey Kosarevsky, 2015
@corporateshark sk@linderdaum.com
http://www.linderdaum.com
http://blog.linderdaum.com
=============================
A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.

0
git Normal file
View File

View File

@@ -13,3 +13,6 @@ endif()
if (ENABLE_QT)
add_subdirectory(yuzu)
endif()
if (ENABLE_WEB_SERVICE)
add_subdirectory(web_service)
endif()

View File

@@ -29,7 +29,7 @@ if ($ENV{CI})
if (BUILD_VERSION)
# This leaves a trailing space on the last word, but we actually want that
# because of how it's styled in the title bar.
set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
else()
set(BUILD_FULLNAME "")
endif()
@@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
add_library(common STATIC
alignment.h
assert.h
detached_tasks.cpp
detached_tasks.h
bit_field.h
bit_set.h
cityhash.cpp
@@ -87,6 +89,7 @@ add_library(common STATIC
timer.cpp
timer.h
vector_math.h
web_result.h
)
if(ARCHITECTURE_x86_64)

View File

@@ -0,0 +1,41 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <thread>
#include "common/assert.h"
#include "common/detached_tasks.h"
namespace Common {
DetachedTasks* DetachedTasks::instance = nullptr;
DetachedTasks::DetachedTasks() {
ASSERT(instance == nullptr);
instance = this;
}
void DetachedTasks::WaitForAllTasks() {
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [this]() { return count == 0; });
}
DetachedTasks::~DetachedTasks() {
std::unique_lock<std::mutex> lock(mutex);
ASSERT(count == 0);
instance = nullptr;
}
void DetachedTasks::AddTask(std::function<void()> task) {
std::unique_lock<std::mutex> lock(instance->mutex);
++instance->count;
std::thread([task{std::move(task)}]() {
task();
std::unique_lock<std::mutex> lock(instance->mutex);
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
})
.detach();
}
} // namespace Common

View File

@@ -0,0 +1,40 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <condition_variable>
#include <functional>
namespace Common {
/**
* A background manager which ensures that all detached task is finished before program exits.
*
* Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
* about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
* the task is launched just before the program exits (which is a common case for telemetry), so we
* need to block on these tasks on program exit.
*
* To make detached task safe, a single DetachedTasks object should be placed in the main(), and
* call WaitForAllTasks() after all program execution but before global/static variable destruction.
* Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
*/
class DetachedTasks {
public:
DetachedTasks();
~DetachedTasks();
void WaitForAllTasks();
static void AddTask(std::function<void()> task);
private:
static DetachedTasks* instance;
std::condition_variable cv;
std::mutex mutex;
int count = 0;
};
} // namespace Common

View File

@@ -285,7 +285,7 @@ private:
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
#else
fstream.open(filename.c_str(), openmode);
#endif

View File

@@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) {
return 0;
}
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
std::vector<u8> out(str.size() / 2);
if (little_endian) {
for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
} else {
for (std::size_t i = 0; i < str.size(); i += 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
}
return out;
}
std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
std::string out;
for (u8 c : vector)
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
return out;
}
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
if (len != 32) {
LOG_ERROR(Common,

View File

@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
@@ -14,6 +15,8 @@ namespace Common {
u8 ToHexNibble(char c1);
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
template <std::size_t Size, bool le = false>
std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
@@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
return out;
}
std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
template <std::size_t Size>
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;

View File

@@ -31,7 +31,7 @@ std::string FormatLogMessage(const Entry& entry) {
}
void PrintMessage(const Entry& entry) {
auto str = FormatLogMessage(entry) + '\n';
const auto str = FormatLogMessage(entry).append(1, '\n');
fputs(str.c_str(), stderr);
}

View File

@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
/// A placeholder for empty param packages to avoid empty strings
/// (they may be recognized as "not set" by some frontend libraries like qt)
constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
ParamPackage::ParamPackage(const std::string& serialized) {
if (serialized == EMPTY_PLACEHOLDER) {
return;
}
std::vector<std::string> pairs;
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
std::string ParamPackage::Serialize() const {
if (data.empty())
return "";
return EMPTY_PLACEHOLDER;
std::string result;
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
return data.find(key) != data.end();
}
void ParamPackage::Erase(const std::string& key) {
data.erase(key);
}
void ParamPackage::Clear() {
data.clear();
}
} // namespace Common

View File

@@ -32,6 +32,8 @@ public:
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
bool Has(const std::string& key) const;
void Erase(const std::string& key);
void Clear();
private:
DataType data;

View File

@@ -5,6 +5,7 @@
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <codecvt>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -13,11 +14,7 @@
#include "common/string_util.h"
#ifdef _WIN32
#include <codecvt>
#include <windows.h>
#include "common/common_funcs.h"
#else
#include <iconv.h>
#endif
namespace Common {
@@ -195,11 +192,9 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
return result;
}
#ifdef _WIN32
std::string UTF16ToUTF8(const std::u16string& input) {
#if _MSC_VER >= 1900
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
#ifdef _MSC_VER
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
return convert.to_bytes(tmp_buffer);
@@ -210,8 +205,8 @@ std::string UTF16ToUTF8(const std::u16string& input) {
}
std::u16string UTF8ToUTF16(const std::string& input) {
#if _MSC_VER >= 1900
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
#ifdef _MSC_VER
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
auto tmp_buffer = convert.from_bytes(input);
return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
@@ -221,6 +216,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
#endif
}
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
const auto size =
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
@@ -261,124 +257,6 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
return CPToUTF16(CP_UTF8, input);
}
std::string SHIFTJISToUTF8(const std::string& input) {
return UTF16ToUTF8(CPToUTF16(932, input));
}
std::string CP1252ToUTF8(const std::string& input) {
return UTF16ToUTF8(CPToUTF16(1252, input));
}
#else
template <typename T>
static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
if ((iconv_t)(-1) == conv_desc) {
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
iconv_close(conv_desc);
return {};
}
const std::size_t in_bytes = sizeof(T) * input.size();
// Multiply by 4, which is the max number of bytes to encode a codepoint
const std::size_t out_buffer_size = 4 * in_bytes;
std::string out_buffer(out_buffer_size, '\0');
auto src_buffer = &input[0];
std::size_t src_bytes = in_bytes;
auto dst_buffer = &out_buffer[0];
std::size_t dst_bytes = out_buffer.size();
while (0 != src_bytes) {
std::size_t const iconv_result =
iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes);
if (static_cast<std::size_t>(-1) == iconv_result) {
if (EILSEQ == errno || EINVAL == errno) {
// Try to skip the bad character
if (0 != src_bytes) {
--src_bytes;
++src_buffer;
}
} else {
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
break;
}
}
}
std::string result;
out_buffer.resize(out_buffer_size - dst_bytes);
out_buffer.swap(result);
iconv_close(conv_desc);
return result;
}
std::u16string UTF8ToUTF16(const std::string& input) {
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
if ((iconv_t)(-1) == conv_desc) {
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
iconv_close(conv_desc);
return {};
}
const std::size_t in_bytes = sizeof(char) * input.size();
// Multiply by 4, which is the max number of bytes to encode a codepoint
const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
std::u16string out_buffer(out_buffer_size, char16_t{});
char* src_buffer = const_cast<char*>(&input[0]);
std::size_t src_bytes = in_bytes;
char* dst_buffer = (char*)(&out_buffer[0]);
std::size_t dst_bytes = out_buffer.size();
while (0 != src_bytes) {
std::size_t const iconv_result =
iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes);
if (static_cast<std::size_t>(-1) == iconv_result) {
if (EILSEQ == errno || EINVAL == errno) {
// Try to skip the bad character
if (0 != src_bytes) {
--src_bytes;
++src_buffer;
}
} else {
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
break;
}
}
}
std::u16string result;
out_buffer.resize(out_buffer_size - dst_bytes);
out_buffer.swap(result);
iconv_close(conv_desc);
return result;
}
std::string UTF16ToUTF8(const std::u16string& input) {
return CodeToUTF8("UTF-16LE", input);
}
std::string CP1252ToUTF8(const std::string& input) {
// return CodeToUTF8("CP1252//TRANSLIT", input);
// return CodeToUTF8("CP1252//IGNORE", input);
return CodeToUTF8("CP1252", input);
}
std::string SHIFTJISToUTF8(const std::string& input) {
// return CodeToUTF8("CP932", input);
return CodeToUTF8("SJIS", input);
}
#endif
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) {

View File

@@ -72,31 +72,10 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
std::string UTF16ToUTF8(const std::u16string& input);
std::u16string UTF8ToUTF16(const std::string& input);
std::string CP1252ToUTF8(const std::string& str);
std::string SHIFTJISToUTF8(const std::string& str);
#ifdef _WIN32
std::string UTF16ToUTF8(const std::wstring& input);
std::wstring UTF8ToUTF16W(const std::string& str);
#ifdef _UNICODE
inline std::string TStrToUTF8(const std::wstring& str) {
return UTF16ToUTF8(str);
}
inline std::wstring UTF8ToTStr(const std::string& str) {
return UTF8ToUTF16W(str);
}
#else
inline std::string TStrToUTF8(const std::string& str) {
return str;
}
inline std::string UTF8ToTStr(const std::string& str) {
return str;
}
#endif
#endif
/**

24
src/common/web_result.h Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
namespace Common {
struct WebResult {
enum class Code : u32 {
Success,
InvalidURL,
CredentialsMissing,
LibError,
HttpError,
WrongContent,
NoWebservice,
};
Code result_code;
std::string result_string;
std::string returned_data;
};
} // namespace Common

View File

@@ -34,6 +34,8 @@ add_library(core STATIC
file_sys/errors.h
file_sys/fsmitm_romfsbuild.cpp
file_sys/fsmitm_romfsbuild.h
file_sys/ips_layer.cpp
file_sys/ips_layer.h
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
@@ -394,6 +396,10 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
add_definitions(-DENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service)
endif()
if (ARCHITECTURE_x86_64)
target_sources(core PRIVATE

View File

@@ -86,7 +86,7 @@ public:
parent.jit->HaltExecution();
parent.SetPC(pc);
Kernel::Thread* thread = Kernel::GetCurrentThread();
parent.SaveContext(thread->context);
parent.SaveContext(thread->GetContext());
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
return;

View File

@@ -195,7 +195,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
}
Kernel::Thread* thread = Kernel::GetCurrentThread();
SaveContext(thread->context);
SaveContext(thread->GetContext());
if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;
GDBStub::Break();

View File

@@ -20,7 +20,9 @@ namespace FileSys {
constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(0x4) {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;

View File

@@ -26,6 +26,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "core/file_sys/fsmitm_romfsbuild.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_vector.h"
@@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
return count;
}
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
@@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
continue;
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
@@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
continue;
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
child->source = root_romfs->GetFileRelative(child->path);
if (ext != nullptr) {
const auto ips = ext->GetFileRelative(child->path + ".ips");
if (ips != nullptr) {
auto patched = PatchIPS(child->source, ips);
if (patched != nullptr)
child->source = std::move(patched);
}
}
child->size = child->source->GetSize();
AddFile(parent, child);
@@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
}
for (auto& child : child_dirs) {
this->VisitDirectory(root_romfs, child);
this->VisitDirectory(root_romfs, ext, child);
}
}
@@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
return true;
}
RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
: base(std::move(base_)), ext(std::move(ext_)) {
root = std::make_shared<RomFSBuildDirectoryContext>();
root->path = "\0";
directories.emplace(root->path, root);
num_dirs = 1;
dir_table_size = 0x18;
VisitDirectory(base, root);
VisitDirectory(base, ext, root);
}
RomFSBuildContext::~RomFSBuildContext() = default;

View File

@@ -40,7 +40,7 @@ struct RomFSFileEntry;
class RomFSBuildContext {
public:
explicit RomFSBuildContext(VirtualDir base);
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
~RomFSBuildContext();
// This finalizes the context.
@@ -48,6 +48,7 @@ public:
private:
VirtualDir base;
VirtualDir ext;
std::shared_ptr<RomFSBuildDirectoryContext> root;
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
@@ -59,7 +60,8 @@ private:
u64 file_hash_table_size = 0;
u64 file_partition_size = 0;
void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
void VisitDirectory(VirtualDir filesys, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent);
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);

View File

@@ -0,0 +1,297 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include "common/assert.h"
#include "common/hex_util.h"
#include "common/swap.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
enum class IPSFileType {
IPS,
IPS32,
Error,
};
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{
std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"},
{"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"},
{"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"},
};
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
if (magic.size() != 5)
return IPSFileType::Error;
if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
return IPSFileType::IPS;
if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
return IPSFileType::IPS32;
return IPSFileType::Error;
}
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr)
return nullptr;
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
if (type == IPSFileType::Error)
return nullptr;
auto in_data = in->ReadAllBytes();
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
offset += temp.size();
if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
break;
}
u32 real_offset{};
if (type == IPSFileType::IPS32)
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
u16 data_size{};
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
return nullptr;
data_size = Common::swap16(data_size);
offset += sizeof(u16);
if (data_size == 0) { // RLE
u16 rle_size{};
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
return nullptr;
rle_size = Common::swap16(data_size);
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);
if (data == boost::none)
return nullptr;
if (real_offset + rle_size > in_data.size())
rle_size = in_data.size() - real_offset;
std::memset(in_data.data() + real_offset, data.get(), rle_size);
} else { // Standard Patch
auto read = data_size;
if (real_offset + read > in_data.size())
read = in_data.size() - real_offset;
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr;
offset += data_size;
}
}
if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
return nullptr;
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
}
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse();
}
IPSwitchCompiler::~IPSwitchCompiler() = default;
std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id;
}
bool IPSwitchCompiler::IsValid() const {
return valid;
}
static bool StartsWith(std::string_view base, std::string_view check) {
return base.size() >= check.size() && base.substr(0, check.size()) == check;
}
static std::string EscapeStringSequences(std::string in) {
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
for (auto index = in.find(seq.first); index != std::string::npos;
index = in.find(seq.first, index)) {
in.replace(index, std::strlen(seq.first), seq.second);
index += std::strlen(seq.second);
}
}
return in;
}
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
offset_shift = std::stoll(line.substr(19), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
} else if (StartsWith(line, "@big-endian")) {
// Set values to read as big endian
is_little_endian = false;
} else if (StartsWith(line, "@flag print_values")) {
// Force printing of applied values
print_values = true;
}
}
void IPSwitchCompiler::Parse() {
const auto bytes = patch_text->ReadAllBytes();
std::stringstream s;
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
std::vector<std::string> lines;
std::string stream_line;
while (std::getline(s, stream_line)) {
// Remove a trailing \r
if (!stream_line.empty() && stream_line.back() == '\r')
stream_line.pop_back();
lines.push_back(std::move(stream_line));
}
for (std::size_t i = 0; i < lines.size(); ++i) {
auto line = lines[i];
// Remove midline comments
std::size_t comment_index = std::string::npos;
bool within_string = false;
for (std::size_t k = 0; k < line.size(); ++k) {
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
within_string = !within_string;
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
comment_index = k;
break;
}
}
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
last_comment = line.substr(comment_index + 2);
line = line.substr(0, comment_index);
}
if (StartsWith(line, "@stop")) {
// Force stop
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
auto raw_build_id = line.substr(8);
if (raw_build_id.size() != 0x40)
raw_build_id.resize(0x40, '0');
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
patch_text->GetName(), line.substr(1));
} else if (StartsWith(line, "//")) {
// Normal Comment
last_comment = line.substr(2);
if (last_comment.find_first_not_of(' ') == std::string::npos)
continue;
if (last_comment.find_first_not_of(' ') != 0)
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
// Start of patch
const auto enabled = StartsWith(line, "@enabled");
if (i == 0)
return;
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
patch_text->GetName(), last_comment, line.substr(1));
IPSwitchPatch patch{last_comment, enabled, {}};
// Read rest of patch
while (true) {
if (i + 1 >= lines.size())
break;
const auto patch_line = lines[++i];
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
--i;
break;
}
// Check for a flag
if (StartsWith(patch_line, "@")) {
ParseFlag(patch_line);
continue;
}
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
offset += offset_shift;
std::vector<u8> replace;
// 9 - first char of replacement val
if (patch_line[9] == '\"') {
// string replacement
auto end_index = patch_line.find('\"', 10);
if (end_index == std::string::npos || end_index < 10)
return;
while (patch_line[end_index - 1] == '\\') {
end_index = patch_line.find('\"', end_index + 1);
if (end_index == std::string::npos || end_index < 10)
return;
}
auto value = patch_line.substr(10, end_index - 10);
value = EscapeStringSequences(value);
replace.reserve(value.size());
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
const auto value = patch_line.substr(9);
replace.reserve(value.size() / 2);
replace = Common::HexStringToVector(value, is_little_endian);
}
if (print_values) {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
"with byte string '{}'",
patch_text->GetName(), offset, Common::HexVectorToString(replace));
}
patch.records.insert_or_assign(offset, std::move(replace));
}
patches.push_back(std::move(patch));
} else if (StartsWith(line, "@")) {
ParseFlag(line);
}
}
valid = true;
}
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr || !valid)
return nullptr;
auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) {
if (!patch.enabled)
continue;
for (const auto& record : patch.records) {
if (record.first >= in_data.size())
continue;
auto replace_size = record.second.size();
if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first;
for (std::size_t i = 0; i < replace_size; ++i)
in_data[i + record.first] = record.second[i];
}
}
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
}
} // namespace FileSys

View File

@@ -0,0 +1,45 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/file_sys/vfs.h"
namespace FileSys {
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
class IPSwitchCompiler {
public:
explicit IPSwitchCompiler(VirtualFile patch_text);
~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const;
private:
void ParseFlag(const std::string& flag);
void Parse();
bool valid = false;
struct IPSwitchPatch {
std::string name;
bool enabled;
std::map<u32, std::vector<u8>> records;
};
VirtualFile patch_text;
std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
};
} // namespace FileSys

View File

@@ -5,14 +5,18 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_layered.h"
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
@@ -21,6 +25,14 @@ namespace FileSys {
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
struct NSOBuildHeader {
u32_le magic;
INSERT_PADDING_BYTES(0x3C);
std::array<u8, 0x20> build_id;
INSERT_PADDING_BYTES(0xA0);
};
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
std::array<u8, sizeof(u32)> bytes{};
bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -34,16 +46,6 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
constexpr std::array<const char*, 3> PATCH_TYPE_NAMES{
"Update",
"LayeredFS",
"DLC",
};
std::string FormatPatchTypeName(PatchType type) {
return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type));
}
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
@@ -71,6 +73,99 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) {
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
auto name = file->GetName();
const auto p1 = name.substr(0, name.find('.'));
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
if (build_id == this_build_id)
out.push_back(file);
} else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
this_build_id =
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
if (build_id == this_build_id)
out.push_back(file);
}
}
}
}
return out;
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
if (nso.size() < 0x100)
return nso;
NSOBuildHeader header;
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return nso;
const auto build_id_raw = Common::HexArrayToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso;
for (const auto& patch_file : patches) {
if (patch_file->GetExtension() == "ips") {
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
if (patched != nullptr)
out = patched->ReadAllBytes();
} else if (patch_file->GetExtension() == "pchtxt") {
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const IPSwitchCompiler compiler{patch_file};
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
if (patched != nullptr)
out = patched->ReadAllBytes();
}
}
if (out.size() < 0x100)
return nso;
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
return out;
}
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
const auto build_id_raw = Common::HexArrayToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
return !CollectPatches(patch_dirs, build_id).empty();
}
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
@@ -87,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
std::vector<VirtualDir> layers;
std::vector<VirtualDir> layers_ext;
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
auto ext_dir = subdir->GetSubdirectory("romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::move(ext_dir));
}
layers.push_back(std::move(extracted));
@@ -100,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
auto packed = CreateRomFS(std::move(layered));
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
if (packed == nullptr) {
return;
}
@@ -109,8 +212,8 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
ContentRecordType type) const {
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
static_cast<u8>(type));
@@ -130,6 +233,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
romfs = new_nca->GetRomFS();
}
}
// LayeredFS
@@ -138,8 +248,20 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
return romfs;
}
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
std::map<PatchType, std::string> out;
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
if (to.empty())
to += with;
else
to += ", " + with;
}
static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
// Game Updates
@@ -148,23 +270,54 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
auto [nacp, discard_icon_file] = update.GetControlMetadata();
if (nacp != nullptr) {
out[PatchType::Update] = nacp->GetVersionString();
out.insert_or_assign("Update", nacp->GetVersionString());
} else {
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed->GetEntryVersion(update_tid);
if (meta_ver == boost::none || meta_ver.get() == 0) {
out[PatchType::Update] = "";
out.insert_or_assign("Update", "");
} else {
out[PatchType::Update] =
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
out.insert_or_assign(
"Update",
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
out.insert_or_assign("Update", "PACKED");
}
}
// LayeredFS
const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
out.insert_or_assign(PatchType::LayeredFS, "");
// General Mods (LayeredFS and IPS)
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
const auto exefs_dir = mod->GetSubdirectory("exefs");
if (IsDirValidAndNonEmpty(exefs_dir)) {
bool ips = false;
bool ipswitch = false;
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips")
ips = true;
else if (file->GetExtension() == "pchtxt")
ipswitch = true;
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
if (types.empty())
continue;
out.insert_or_assign(mod->GetName(), types);
}
}
// DLC
const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
@@ -186,7 +339,7 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
out.insert_or_assign(PatchType::DLC, std::move(list));
out.insert_or_assign("DLC", std::move(list));
}
return out;

View File

@@ -24,14 +24,6 @@ enum class TitleVersionFormat : u8 {
std::string FormatTitleVersion(u32 version,
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
enum class PatchType {
Update,
LayeredFS,
DLC,
};
std::string FormatPatchTypeName(PatchType type);
// A centralized class to manage patches to games.
class PatchManager {
public:
@@ -42,15 +34,26 @@ public:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program) const;
ContentRecordType type = ContentRecordType::Program,
VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update v80 will return {Update, 80}
std::map<PatchType, std::string> GetPatchVersionNames() const;
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
VirtualFile update_raw = nullptr) const;
// Given title_id of the program, attempts to get the control data of the update and parse it,
// falling back to the base control data.

View File

@@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
return out;
}
VirtualFile CreateRomFS(VirtualDir dir) {
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
if (dir == nullptr)
return nullptr;
RomFSBuildContext ctx{dir};
RomFSBuildContext ctx{dir, ext};
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
}

View File

@@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file,
// Converts a VFS filesystem into a RomFS binary
// Returns nullptr on failure
VirtualFile CreateRomFS(VirtualDir dir);
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
} // namespace FileSys

View File

@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
RomFSFactory::~RomFSFactory() = default;
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
this->update_raw = std::move(update_raw);
}
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
if (!updatable)
return MakeResult<VirtualFile>(file);
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {

View File

@@ -32,11 +32,13 @@ public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
~RomFSFactory();
void SetPackedUpdate(VirtualFile update_raw);
ResultVal<VirtualFile> OpenCurrentProcess();
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
private:
VirtualFile file;
VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
};

View File

@@ -18,6 +18,39 @@
#include "core/loader/loader.h"
namespace FileSys {
namespace {
void SetTicketKeys(const std::vector<VirtualFile>& files) {
Core::Crypto::KeyManager keys;
for (const auto& ticket_file : files) {
if (ticket_file == nullptr) {
continue;
}
if (ticket_file->GetExtension() != "tik") {
continue;
}
if (ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
Core::Crypto::Key128 key{};
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
// We get the name without the extension in order to create the rights ID.
std::string name_only(ticket_file->GetName());
name_only.erase(name_only.size() - 4);
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
u128 rights_id;
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
} // Anonymous namespace
NSP::NSP(VirtualFile file_)
: file(std::move(file_)), status{Loader::ResultStatus::Success},
pfs(std::make_shared<PartitionFilesystem>(file)) {
@@ -26,83 +59,16 @@ NSP::NSP(VirtualFile file_)
return;
}
const auto files = pfs->GetFiles();
if (IsDirectoryExeFS(pfs)) {
extracted = true;
exefs = pfs;
const auto& files = pfs->GetFiles();
const auto romfs_iter =
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
return file->GetName().find(".romfs") != std::string::npos;
});
if (romfs_iter != files.end())
romfs = *romfs_iter;
InitializeExeFSAndRomFS(files);
return;
}
extracted = false;
const auto files = pfs->GetFiles();
Core::Crypto::KeyManager keys;
for (const auto& ticket_file : files) {
if (ticket_file->GetExtension() == "tik") {
if (ticket_file == nullptr ||
ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
Core::Crypto::Key128 key{};
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
std::string_view name_only(ticket_file->GetName());
name_only.remove_suffix(4);
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
u128 rights_id;
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
for (const auto& outer_file : files) {
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
const auto nca = std::make_shared<NCA>(outer_file);
if (nca->GetStatus() != Loader::ResultStatus::Success) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
if (inner_file->GetExtension() != "cnmt")
continue;
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[ContentRecordType::Meta] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
"be found in PFS. NSP appears to be corrupted.",
id_string);
continue;
}
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
ncas_title[rec.type] = std::move(next_nca);
}
break;
}
}
}
SetTicketKeys(files);
ReadNCAs(files);
}
NSP::~NSP() = default;
@@ -242,4 +208,66 @@ VirtualDir NSP::GetParentDirectory() const {
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
const auto romfs_iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& file) {
return file->GetName().rfind(".romfs") != std::string::npos;
});
if (romfs_iter == files.end()) {
return;
}
romfs = *romfs_iter;
}
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
for (const auto& outer_file : files) {
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
continue;
}
const auto nca = std::make_shared<NCA>(outer_file);
if (nca->GetStatus() != Loader::ResultStatus::Success) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
if (inner_file->GetExtension() != "cnmt")
continue;
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[ContentRecordType::Meta] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
"be found in PFS. NSP appears to be corrupted.",
id_string);
continue;
}
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
(cnmt.GetTitleID() & 0x800) != 0)) {
ncas_title[rec.type] = std::move(next_nca);
}
}
break;
}
}
}
} // namespace FileSys

View File

@@ -59,9 +59,12 @@ protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
VirtualFile file;
bool extracted;
bool extracted = false;
Loader::ResultStatus status;
std::map<u64, Loader::ResultStatus> program_status;

View File

@@ -209,7 +209,7 @@ static Kernel::Thread* FindThreadById(int id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
if (thread->GetThreadID() == static_cast<u32>(id)) {
current_core = core;
return thread.get();
}
@@ -223,16 +223,18 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
return 0;
}
const auto& thread_context = thread->GetContext();
if (id < SP_REGISTER) {
return thread->context.cpu_registers[id];
return thread_context.cpu_registers[id];
} else if (id == SP_REGISTER) {
return thread->context.sp;
return thread_context.sp;
} else if (id == PC_REGISTER) {
return thread->context.pc;
return thread_context.pc;
} else if (id == PSTATE_REGISTER) {
return thread->context.pstate;
return thread_context.pstate;
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0];
return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
} else {
return 0;
}
@@ -243,16 +245,18 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
return;
}
auto& thread_context = thread->GetContext();
if (id < SP_REGISTER) {
thread->context.cpu_registers[id] = val;
thread_context.cpu_registers[id] = val;
} else if (id == SP_REGISTER) {
thread->context.sp = val;
thread_context.sp = val;
} else if (id == PC_REGISTER) {
thread->context.pc = val;
thread_context.pc = val;
} else if (id == PSTATE_REGISTER) {
thread->context.pstate = static_cast<u32>(val);
thread_context.pstate = static_cast<u32>(val);
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
}
}
@@ -595,7 +599,7 @@ static void HandleQuery() {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x}", thread->GetThreadId());
val += fmt::format("{:x}", thread->GetThreadID());
val += ",";
}
}
@@ -612,7 +616,7 @@ static void HandleQuery() {
for (const auto& thread : threads) {
buffer +=
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
thread->GetThreadId(), core, thread->GetThreadId());
thread->GetThreadID(), core, thread->GetThreadID());
}
}
buffer += "</threads>";
@@ -693,7 +697,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
}
if (thread) {
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
}
SendReply(buffer.c_str());
@@ -857,7 +861,9 @@ static void WriteRegister() {
}
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
SendReply("OK");
}
@@ -886,7 +892,9 @@ static void WriteRegisters() {
}
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
SendReply("OK");
}
@@ -960,7 +968,9 @@ static void Step() {
if (command_length > 1) {
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
}
step_loop = true;
halt_loop = true;

View File

@@ -23,13 +23,13 @@ namespace AddressArbiter {
// Performs actual address waiting logic.
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
SharedPtr<Thread> current_thread = GetCurrentThread();
current_thread->arb_wait_address = address;
current_thread->status = ThreadStatus::WaitArb;
current_thread->wakeup_callback = nullptr;
current_thread->SetArbiterWaitAddress(address);
current_thread->SetStatus(ThreadStatus::WaitArb);
current_thread->InvalidateWakeupCallback();
current_thread->WakeAfterDelay(timeout);
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
return RESULT_TIMEOUT;
}
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr arb_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
auto& thread_list = scheduler->GetThreadList();
const auto& thread_list = scheduler->GetThreadList();
for (auto& thread : thread_list) {
if (thread->arb_wait_address == arb_addr)
for (const auto& thread : thread_list) {
if (thread->GetArbiterWaitAddress() == arb_addr)
waiting_threads.push_back(thread);
}
};
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
// Sort them by priority, such that the highest priority ones come first.
std::sort(threads.begin(), threads.end(),
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
return lhs->current_priority < rhs->current_priority;
return lhs->GetPriority() < rhs->GetPriority();
});
return threads;
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
// Signal the waiting threads.
for (std::size_t i = 0; i < last; i++) {
ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb);
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
waiting_threads[i]->arb_wait_address = 0;
waiting_threads[i]->SetArbiterWaitAddress(0);
waiting_threads[i]->ResumeFromWait();
}
}

View File

@@ -17,6 +17,10 @@ namespace Kernel {
ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
ClientPort::~ClientPort() = default;
SharedPtr<ServerPort> ClientPort::GetServerPort() const {
return server_port;
}
ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
// Note: Threads do not wait for the server endpoint to call
// AcceptSession before returning from this call.

View File

@@ -30,6 +30,8 @@ public:
return HANDLE_TYPE;
}
SharedPtr<ServerPort> GetServerPort() const;
/**
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
* list of pending sessions, and signals the ServerPort, causing any threads

View File

@@ -42,14 +42,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
Kernel::SharedPtr<Kernel::Event> event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->wakeup_callback = [context = *this, callback](
thread->SetWakeupCallback([context = *this, callback](
ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
ASSERT(thread->status == ThreadStatus::WaitHLEEvent);
ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
callback(thread, context, reason);
context.WriteToOutgoingCommandBuffer(*thread);
return true;
};
});
if (!event) {
// Create event if not provided
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
}
event->Clear();
thread->status = ThreadStatus::WaitHLEEvent;
thread->wait_objects = {event};
thread->SetStatus(ThreadStatus::WaitHLEEvent);
thread->SetWaitObjects({event});
event->AddWaitingThread(thread);
if (timeout > 0) {
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
// The header was already built in the internal command buffer. Attempt to parse it to verify
@@ -268,7 +268,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
}
// Copy the translated command buffer back into the thread's command buffer area.
Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
return RESULT_SUCCESS;

View File

@@ -46,40 +46,40 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
bool resume = true;
if (thread->status == ThreadStatus::WaitSynchAny ||
thread->status == ThreadStatus::WaitSynchAll ||
thread->status == ThreadStatus::WaitHLEEvent) {
if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
thread->GetStatus() == ThreadStatus::WaitSynchAll ||
thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects) {
for (const auto& object : thread->GetWaitObjects()) {
object->RemoveWaitingThread(thread.get());
}
thread->wait_objects.clear();
thread->ClearWaitObjects();
// Invoke the wakeup callback before clearing the wait objects
if (thread->wakeup_callback) {
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
if (thread->HasWakeupCallback()) {
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
}
}
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
thread->wait_handle) {
ASSERT(thread->status == ThreadStatus::WaitMutex);
thread->mutex_wait_address = 0;
thread->condvar_wait_address = 0;
thread->wait_handle = 0;
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
thread->GetWaitHandle() != 0) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->SetMutexWaitAddress(0);
thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);
auto lock_owner = thread->lock_owner;
auto* const lock_owner = thread->GetLockOwner();
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
// wasn't awakened due to the mutex already being acquired.
if (lock_owner) {
if (lock_owner != nullptr) {
lock_owner->RemoveMutexWaiter(thread);
}
}
if (thread->arb_wait_address != 0) {
ASSERT(thread->status == ThreadStatus::WaitArb);
thread->arb_wait_address = 0;
if (thread->GetArbiterWaitAddress() != 0) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
thread->SetArbiterWaitAddress(0);
}
if (resume) {

View File

@@ -28,11 +28,11 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
SharedPtr<Thread> highest_priority_thread;
u32 num_waiters = 0;
for (auto& thread : current_thread->wait_mutex_threads) {
if (thread->mutex_wait_address != mutex_addr)
for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
if (thread->GetMutexWaitAddress() != mutex_addr)
continue;
ASSERT(thread->status == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
++num_waiters;
if (highest_priority_thread == nullptr ||
@@ -47,12 +47,12 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
SharedPtr<Thread> new_owner) {
auto threads = current_thread->wait_mutex_threads;
for (auto& thread : threads) {
if (thread->mutex_wait_address != mutex_addr)
const auto threads = current_thread->GetMutexWaitingThreads();
for (const auto& thread : threads) {
if (thread->GetMutexWaitAddress() != mutex_addr)
continue;
ASSERT(thread->lock_owner == current_thread);
ASSERT(thread->GetLockOwner() == current_thread);
current_thread->RemoveMutexWaiter(thread);
if (new_owner != thread)
new_owner->AddMutexWaiter(thread);
@@ -84,11 +84,11 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho
return ERR_INVALID_HANDLE;
// Wait until the mutex is released
GetCurrentThread()->mutex_wait_address = address;
GetCurrentThread()->wait_handle = requesting_thread_handle;
GetCurrentThread()->SetMutexWaitAddress(address);
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
GetCurrentThread()->status = ThreadStatus::WaitMutex;
GetCurrentThread()->wakeup_callback = nullptr;
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
GetCurrentThread()->InvalidateWakeupCallback();
// Update the lock holder thread's priority to prevent priority inversion.
holding_thread->AddMutexWaiter(GetCurrentThread());
@@ -115,7 +115,7 @@ ResultCode Mutex::Release(VAddr address) {
// Transfer the ownership of the mutex from the previous owner to the new one.
TransferMutexOwnership(address, GetCurrentThread(), thread);
u32 mutex_value = thread->wait_handle;
u32 mutex_value = thread->GetWaitHandle();
if (num_waiters >= 2) {
// Notify the guest that there are still some threads waiting for the mutex
@@ -125,13 +125,13 @@ ResultCode Mutex::Release(VAddr address) {
// Grant the mutex to the next waiting thread and resume it.
Memory::Write32(address, mutex_value);
ASSERT(thread->status == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->ResumeFromWait();
thread->lock_owner = nullptr;
thread->condvar_wait_address = 0;
thread->mutex_wait_address = 0;
thread->wait_handle = 0;
thread->SetLockOwner(nullptr);
thread->SetCondVarWaitAddress(0);
thread->SetMutexWaitAddress(0);
thread->SetWaitHandle(0);
return RESULT_SUCCESS;
}

View File

@@ -144,15 +144,15 @@ void Process::PrepareForTermination() {
const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
for (auto& thread : thread_list) {
if (thread->owner_process != this)
if (thread->GetOwnerProcess() != this)
continue;
if (thread == GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
thread->status == ThreadStatus::WaitSynchAll,
ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
thread->GetStatus() == ThreadStatus::WaitSynchAll,
"Exiting processes with non-waiting threads is currently unimplemented");
thread->Stop();

View File

@@ -38,10 +38,10 @@ Thread* Scheduler::PopNextReadyThread() {
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
if (thread && thread->status == ThreadStatus::Running) {
if (thread && thread->GetStatus() == ThreadStatus::Running) {
// We have to do better than the current thread.
// This call returns null when that's not possible.
next = ready_queue.pop_first_better(thread->current_priority);
next = ready_queue.pop_first_better(thread->GetPriority());
if (!next) {
// Otherwise just keep going with the current thread
next = thread;
@@ -58,22 +58,21 @@ void Scheduler::SwitchContext(Thread* new_thread) {
// Save context for previous thread
if (previous_thread) {
previous_thread->last_running_ticks = CoreTiming::GetTicks();
cpu_core.SaveContext(previous_thread->context);
cpu_core.SaveContext(previous_thread->GetContext());
// Save the TPIDR_EL0 system register in case it was modified.
previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0();
previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
if (previous_thread->status == ThreadStatus::Running) {
if (previous_thread->GetStatus() == ThreadStatus::Running) {
// This is only the case when a reschedule is triggered without the current thread
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
ready_queue.push_front(previous_thread->current_priority, previous_thread);
previous_thread->status = ThreadStatus::Ready;
ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
previous_thread->SetStatus(ThreadStatus::Ready);
}
}
// Load context of new thread
if (new_thread) {
ASSERT_MSG(new_thread->status == ThreadStatus::Ready,
ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
"Thread must be ready to become running.");
// Cancel any outstanding wakeup events for this thread
@@ -83,15 +82,16 @@ void Scheduler::SwitchContext(Thread* new_thread) {
current_thread = new_thread;
ready_queue.remove(new_thread->current_priority, new_thread);
new_thread->status = ThreadStatus::Running;
ready_queue.remove(new_thread->GetPriority(), new_thread);
new_thread->SetStatus(ThreadStatus::Running);
if (previous_process != current_thread->owner_process) {
Core::CurrentProcess() = current_thread->owner_process;
const auto thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
Core::CurrentProcess() = thread_owner_process;
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
}
cpu_core.LoadContext(new_thread->context);
cpu_core.LoadContext(new_thread->GetContext());
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
@@ -136,14 +136,14 @@ void Scheduler::RemoveThread(Thread* thread) {
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
ASSERT(thread->status == ThreadStatus::Ready);
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
ready_queue.push_back(priority, thread);
}
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
ASSERT(thread->status == ThreadStatus::Ready);
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
ready_queue.remove(priority, thread);
}
@@ -151,8 +151,8 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
// If thread was ready, adjust queues
if (thread->status == ThreadStatus::Ready)
ready_queue.move(thread, thread->current_priority, priority);
if (thread->GetStatus() == ThreadStatus::Ready)
ready_queue.move(thread, thread->GetPriority(), priority);
else
ready_queue.prepare(priority);
}

View File

@@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace Kernel {

View File

@@ -120,10 +120,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
result = hle_handler->HandleSyncRequest(context);
}
if (thread->status == ThreadStatus::Running) {
if (thread->GetStatus() == ThreadStatus::Running) {
// Put the thread to sleep until the server replies, it will be awoken in
// svcReplyAndReceive for LLE servers.
thread->status = ThreadStatus::WaitIPC;
thread->SetStatus(ThreadStatus::WaitIPC);
if (hle_handler != nullptr) {
// For HLE services, we put the request threads to sleep for a short duration to

View File

@@ -156,7 +156,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
return ERR_INVALID_HANDLE;
}
*thread_id = thread->GetThreadId();
*thread_id = thread->GetThreadID();
return RESULT_SUCCESS;
}
@@ -177,7 +177,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
/// Default thread wakeup callback for WaitSynchronization
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index) {
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
if (reason == ThreadWakeupReason::Timeout) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
@@ -204,10 +204,10 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
if (handle_count > MaxHandles)
return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge);
auto thread = GetCurrentThread();
auto* const thread = GetCurrentThread();
using ObjectPtr = SharedPtr<WaitObject>;
std::vector<ObjectPtr> objects(handle_count);
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
Thread::ThreadWaitObjects objects(handle_count);
auto& kernel = Core::System::GetInstance().Kernel();
for (u64 i = 0; i < handle_count; ++i) {
@@ -244,14 +244,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
for (auto& object : objects)
object->AddWaitingThread(thread);
thread->wait_objects = std::move(objects);
thread->status = ThreadStatus::WaitSynchAny;
thread->SetWaitObjects(std::move(objects));
thread->SetStatus(ThreadStatus::WaitSynchAny);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
thread->wakeup_callback = DefaultThreadWakeupCallback;
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_TIMEOUT;
}
@@ -266,7 +266,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
return ERR_INVALID_HANDLE;
}
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
thread->SetWaitSynchronizationResult(
ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled));
thread->ResumeFromWait();
@@ -425,7 +425,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
}
const auto current_process = Core::CurrentProcess();
if (thread->owner_process != current_process) {
if (thread->GetOwnerProcess() != current_process) {
return ERR_INVALID_HANDLE;
}
@@ -433,7 +433,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
return ERR_ALREADY_REGISTERED;
}
Core::ARM_Interface::ThreadContext ctx = thread->context;
Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
ctx.pstate &= 0xFF0FFE20;
@@ -479,14 +479,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
thread->SetPriority(priority);
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Get which CPU core is executing the current thread
static u32 GetCurrentProcessorNumber() {
LOG_TRACE(Kernel_SVC, "called");
return GetCurrentThread()->processor_id;
return GetCurrentThread()->GetProcessorID();
}
static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
@@ -622,10 +622,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
CASCADE_RESULT(SharedPtr<Thread> thread,
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
Core::CurrentProcess()));
CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread));
*out_handle = thread->guest_handle;
const auto new_guest_handle = kernel.HandleTable().Create(thread);
if (new_guest_handle.Failed()) {
return new_guest_handle.Code();
}
thread->SetGuestHandle(*new_guest_handle);
*out_handle = *new_guest_handle;
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
LOG_TRACE(Kernel_SVC,
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
@@ -645,10 +649,10 @@ static ResultCode StartThread(Handle thread_handle) {
return ERR_INVALID_HANDLE;
}
ASSERT(thread->status == ThreadStatus::Dormant);
ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
thread->ResumeFromWait();
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
@@ -694,17 +698,17 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
CASCADE_CODE(Mutex::Release(mutex_addr));
SharedPtr<Thread> current_thread = GetCurrentThread();
current_thread->condvar_wait_address = condition_variable_addr;
current_thread->mutex_wait_address = mutex_addr;
current_thread->wait_handle = thread_handle;
current_thread->status = ThreadStatus::WaitMutex;
current_thread->wakeup_callback = nullptr;
current_thread->SetCondVarWaitAddress(condition_variable_addr);
current_thread->SetMutexWaitAddress(mutex_addr);
current_thread->SetWaitHandle(thread_handle);
current_thread->SetStatus(ThreadStatus::WaitMutex);
current_thread->InvalidateWakeupCallback();
current_thread->WakeAfterDelay(nano_seconds);
// Note: Deliberately don't attempt to inherit the lock owner's priority.
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
@@ -713,14 +717,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
condition_variable_addr, target);
auto RetrieveWaitingThreads = [](std::size_t core_index,
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr condvar_addr) {
const auto RetrieveWaitingThreads = [](std::size_t core_index,
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr condvar_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
auto& thread_list = scheduler->GetThreadList();
const auto& thread_list = scheduler->GetThreadList();
for (auto& thread : thread_list) {
if (thread->condvar_wait_address == condvar_addr)
for (const auto& thread : thread_list) {
if (thread->GetCondVarWaitAddress() == condvar_addr)
waiting_threads.push_back(thread);
}
};
@@ -734,7 +738,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
// Sort them by priority, such that the highest priority ones come first.
std::sort(waiting_threads.begin(), waiting_threads.end(),
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
return lhs->current_priority < rhs->current_priority;
return lhs->GetPriority() < rhs->GetPriority();
});
// Only process up to 'target' threads, unless 'target' is -1, in which case process
@@ -750,7 +754,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
for (std::size_t index = 0; index < last; ++index) {
auto& thread = waiting_threads[index];
ASSERT(thread->condvar_wait_address == condition_variable_addr);
ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
@@ -759,42 +763,43 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
// Atomically read the value of the mutex.
u32 mutex_val = 0;
do {
monitor.SetExclusive(current_core, thread->mutex_wait_address);
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
// If the mutex is not yet acquired, acquire it.
mutex_val = Memory::Read32(thread->mutex_wait_address);
mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
if (mutex_val != 0) {
monitor.ClearExclusive();
break;
}
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
thread->wait_handle));
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
thread->GetWaitHandle()));
if (mutex_val == 0) {
// We were able to acquire the mutex, resume this thread.
ASSERT(thread->status == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->ResumeFromWait();
auto lock_owner = thread->lock_owner;
if (lock_owner)
auto* const lock_owner = thread->GetLockOwner();
if (lock_owner != nullptr) {
lock_owner->RemoveMutexWaiter(thread);
}
thread->lock_owner = nullptr;
thread->mutex_wait_address = 0;
thread->condvar_wait_address = 0;
thread->wait_handle = 0;
thread->SetLockOwner(nullptr);
thread->SetMutexWaitAddress(0);
thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);
} else {
// Atomically signal that the mutex now has a waiting thread.
do {
monitor.SetExclusive(current_core, thread->mutex_wait_address);
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
// Ensure that the mutex value is still what we expect.
u32 value = Memory::Read32(thread->mutex_wait_address);
u32 value = Memory::Read32(thread->GetMutexWaitAddress());
// TODO(Subv): When this happens, the kernel just clears the exclusive state and
// retries the initial read for this thread.
ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
mutex_val | Mutex::MutexHasWaitersFlag));
// The mutex is already owned by some other thread, make this thread wait on it.
@@ -802,12 +807,12 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
ASSERT(owner);
ASSERT(thread->status == ThreadStatus::WaitMutex);
thread->wakeup_callback = nullptr;
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->InvalidateWakeupCallback();
owner->AddMutexWaiter(thread);
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
}
}
@@ -913,8 +918,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
return ERR_INVALID_HANDLE;
}
*core = thread->ideal_core;
*mask = thread->affinity_mask;
*core = thread->GetIdealCore();
*mask = thread->GetAffinityMask();
return RESULT_SUCCESS;
}
@@ -930,11 +935,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
}
if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
ASSERT(thread->owner_process->GetDefaultProcessorID() !=
static_cast<u8>(THREADPROCESSORID_DEFAULT));
const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
// Set the target CPU to the one specified in the process' exheader.
core = thread->owner_process->GetDefaultProcessorID();
mask = 1ull << core;
core = default_processor_id;
mask = 1ULL << core;
}
if (mask == 0) {
@@ -945,7 +952,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
if (core == OnlyChangeMask) {
core = thread->ideal_core;
core = thread->GetIdealCore();
} else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
}

View File

@@ -70,7 +70,7 @@ void Thread::Stop() {
void WaitCurrentThread_Sleep() {
Thread* thread = GetCurrentThread();
thread->status = ThreadStatus::WaitSleep;
thread->SetStatus(ThreadStatus::WaitSleep);
}
void ExitCurrentThread() {
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
scheduler = next_scheduler;
scheduler = next_scheduler.get();
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
@@ -233,7 +233,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
thread->name = std::move(name);
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
thread->owner_process = owner_process;
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id);
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
thread->scheduler->AddThread(thread, priority);
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
@@ -269,9 +269,9 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap();
thread->context.cpu_registers[1] = thread->guest_handle;
const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
thread->SetGuestHandle(guest_handle);
thread->GetContext().cpu_registers[1] = guest_handle;
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
thread->ResumeFromWait();
@@ -299,6 +299,18 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + CommandHeaderOffset;
}
void Thread::SetStatus(ThreadStatus new_status) {
if (new_status == status) {
return;
}
if (status == ThreadStatus::Running) {
last_running_ticks = CoreTiming::GetTicks();
}
status = new_status;
}
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
if (thread->lock_owner == this) {
// If the thread is already waiting for this thread to release the mutex, ensure that the
@@ -388,11 +400,23 @@ void Thread::ChangeCore(u32 core, u64 mask) {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
scheduler = next_scheduler;
scheduler = next_scheduler.get();
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
bool Thread::AllWaitObjectsReady() {
return std::none_of(
wait_objects.begin(), wait_objects.end(),
[this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
}
bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index) {
ASSERT(wakeup_callback);
return wakeup_callback(reason, std::move(thread), std::move(object), index);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/**

View File

@@ -65,6 +65,15 @@ public:
using TLSMemory = std::vector<u8>;
using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
using ThreadContext = Core::ARM_Interface::ThreadContext;
using ThreadWaitObjects = std::vector<SharedPtr<WaitObject>>;
using WakeupCallback = std::function<bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index)>;
/**
* Creates and returns a new thread. The new thread is immediately scheduled
* @param kernel The kernel instance this thread will be created under.
@@ -105,6 +114,14 @@ public:
return current_priority;
}
/**
* Gets the thread's nominal priority.
* @return The current thread's nominal priority.
*/
u32 GetNominalPriority() const {
return nominal_priority;
}
/**
* Sets the thread's current priority
* @param priority The new priority
@@ -133,7 +150,7 @@ public:
* Gets the thread's thread ID
* @return The thread's ID
*/
u32 GetThreadId() const {
u32 GetThreadID() const {
return thread_id;
}
@@ -203,6 +220,11 @@ public:
return tpidr_el0;
}
/// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
void SetTPIDR_EL0(u64 value) {
tpidr_el0 = value;
}
/*
* Returns the address of the current thread's command buffer, located in the TLS.
* @returns VAddr of the thread's command buffer.
@@ -218,69 +240,193 @@ public:
return status == ThreadStatus::WaitSynchAll;
}
Core::ARM_Interface::ThreadContext context;
ThreadContext& GetContext() {
return context;
}
u32 thread_id;
const ThreadContext& GetContext() const {
return context;
}
ThreadStatus status;
VAddr entry_point;
VAddr stack_top;
ThreadStatus GetStatus() const {
return status;
}
u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application
u32 current_priority; ///< Current thread priority, can be temporarily changed
void SetStatus(ThreadStatus new_status);
u64 last_running_ticks; ///< CPU tick when thread was last running
u64 GetLastRunningTicks() const {
return last_running_ticks;
}
s32 processor_id;
s32 GetProcessorID() const {
return processor_id;
}
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
u64 tpidr_el0; ///< TPIDR_EL0 read/write system register.
SharedPtr<Process>& GetOwnerProcess() {
return owner_process;
}
SharedPtr<Process> owner_process; ///< Process that owns this thread
const SharedPtr<Process>& GetOwnerProcess() const {
return owner_process;
}
/// Objects that the thread is waiting on, in the same order as they were
// passed to WaitSynchronization1/N.
std::vector<SharedPtr<WaitObject>> wait_objects;
const ThreadWaitObjects& GetWaitObjects() const {
return wait_objects;
}
/// List of threads that are waiting for a mutex that is held by this thread.
std::vector<SharedPtr<Thread>> wait_mutex_threads;
void SetWaitObjects(ThreadWaitObjects objects) {
wait_objects = std::move(objects);
}
/// Thread that owns the lock that this thread is waiting for.
SharedPtr<Thread> lock_owner;
void ClearWaitObjects() {
wait_objects.clear();
}
// If waiting on a ConditionVariable, this is the ConditionVariable address
VAddr condvar_wait_address;
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
Handle wait_handle; ///< The handle used to wait for the mutex.
/// Determines whether all the objects this thread is waiting on are ready.
bool AllWaitObjectsReady();
// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0};
const MutexWaitingThreads& GetMutexWaitingThreads() const {
return wait_mutex_threads;
}
std::string name;
Thread* GetLockOwner() const {
return lock_owner.get();
}
/// Handle used by guest emulated application to access this thread
Handle guest_handle;
void SetLockOwner(SharedPtr<Thread> owner) {
lock_owner = std::move(owner);
}
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle callback_handle;
VAddr GetCondVarWaitAddress() const {
return condvar_wait_address;
}
using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index);
// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
// was waiting via WaitSynchronizationN then the object will be the last object that became
// available. In case of a timeout, the object will be nullptr.
std::function<WakeupCallback> wakeup_callback;
void SetCondVarWaitAddress(VAddr address) {
condvar_wait_address = address;
}
std::shared_ptr<Scheduler> scheduler;
VAddr GetMutexWaitAddress() const {
return mutex_wait_address;
}
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
void SetMutexWaitAddress(VAddr address) {
mutex_wait_address = address;
}
Handle GetWaitHandle() const {
return wait_handle;
}
void SetWaitHandle(Handle handle) {
wait_handle = handle;
}
VAddr GetArbiterWaitAddress() const {
return arb_wait_address;
}
void SetArbiterWaitAddress(VAddr address) {
arb_wait_address = address;
}
void SetGuestHandle(Handle handle) {
guest_handle = handle;
}
bool HasWakeupCallback() const {
return wakeup_callback != nullptr;
}
void SetWakeupCallback(WakeupCallback callback) {
wakeup_callback = std::move(callback);
}
void InvalidateWakeupCallback() {
SetWakeupCallback(nullptr);
}
/**
* Invokes the thread's wakeup callback.
*
* @pre A valid wakeup callback has been set. Violating this precondition
* will cause an assertion to trigger.
*/
bool InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index);
u32 GetIdealCore() const {
return ideal_core;
}
u64 GetAffinityMask() const {
return affinity_mask;
}
private:
explicit Thread(KernelCore& kernel);
~Thread() override;
Core::ARM_Interface::ThreadContext context{};
u32 thread_id = 0;
ThreadStatus status = ThreadStatus::Dormant;
VAddr entry_point = 0;
VAddr stack_top = 0;
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
s32 processor_id = 0;
VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
/// Process that owns this thread
SharedPtr<Process> owner_process;
/// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization1/N.
ThreadWaitObjects wait_objects;
/// List of threads that are waiting for a mutex that is held by this thread.
MutexWaitingThreads wait_mutex_threads;
/// Thread that owns the lock that this thread is waiting for.
SharedPtr<Thread> lock_owner;
/// If waiting on a ConditionVariable, this is the ConditionVariable address
VAddr condvar_wait_address = 0;
/// If waiting on a Mutex, this is the mutex address
VAddr mutex_wait_address = 0;
/// The handle used to wait for the mutex.
Handle wait_handle = 0;
/// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0};
/// Handle used by guest emulated application to access this thread
Handle guest_handle = 0;
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle callback_handle = 0;
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
/// was waiting via WaitSynchronizationN then the object will be the last object that became
/// available. In case of a timeout, the object will be nullptr.
WakeupCallback wakeup_callback;
Scheduler* scheduler = nullptr;
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
std::string name;
};
/**

View File

@@ -35,13 +35,15 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
u32 candidate_priority = THREADPRIO_LOWEST + 1;
for (const auto& thread : waiting_threads) {
const ThreadStatus thread_status = thread->GetStatus();
// The list of waiting threads must not contain threads that are not waiting to be awakened.
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
thread->status == ThreadStatus::WaitSynchAll ||
thread->status == ThreadStatus::WaitHLEEvent,
ASSERT_MSG(thread_status == ThreadStatus::WaitSynchAny ||
thread_status == ThreadStatus::WaitSynchAll ||
thread_status == ThreadStatus::WaitHLEEvent,
"Inconsistent thread statuses in waiting_threads");
if (thread->current_priority >= candidate_priority)
if (thread->GetPriority() >= candidate_priority)
continue;
if (ShouldWait(thread.get()))
@@ -50,16 +52,13 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
// A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
// in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
bool ready_to_run = true;
if (thread->status == ThreadStatus::WaitSynchAll) {
ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
[&thread](const SharedPtr<WaitObject>& object) {
return object->ShouldWait(thread.get());
});
if (thread_status == ThreadStatus::WaitSynchAll) {
ready_to_run = thread->AllWaitObjectsReady();
}
if (ready_to_run) {
candidate = thread.get();
candidate_priority = thread->current_priority;
candidate_priority = thread->GetPriority();
}
}
@@ -75,24 +74,24 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
if (!thread->IsSleepingOnWaitAll()) {
Acquire(thread.get());
} else {
for (auto& object : thread->wait_objects) {
for (const auto& object : thread->GetWaitObjects()) {
ASSERT(!object->ShouldWait(thread.get()));
object->Acquire(thread.get());
}
}
std::size_t index = thread->GetWaitObjectIndex(this);
const std::size_t index = thread->GetWaitObjectIndex(this);
for (auto& object : thread->wait_objects)
for (const auto& object : thread->GetWaitObjects())
object->RemoveWaitingThread(thread.get());
thread->wait_objects.clear();
thread->ClearWaitObjects();
thread->CancelWakeupTimer();
bool resume = true;
if (thread->wakeup_callback)
resume = thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this, index);
if (thread->HasWakeupCallback())
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, this, index);
if (resume)
thread->ResumeFromWait();

View File

@@ -61,13 +61,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
AOC_U::~AOC_U() = default;
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [&current](u64 tid) {
return (tid & DLC_BASE_TITLE_ID_MASK) == current;
}));
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; })));
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
@@ -91,7 +91,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
return;
}
count = std::min<size_t>(out.size() - offset, count);
count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
out.resize(count);

View File

@@ -264,6 +264,15 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
return RESULT_SUCCESS;
}
void SetPackedUpdate(FileSys::VirtualFile update_raw) {
LOG_TRACE(Service_FS, "Setting packed update for romfs");
if (romfs_factory == nullptr)
return;
romfs_factory->SetPackedUpdate(std::move(update_raw));
}
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
LOG_TRACE(Service_FS, "Opening RomFS for current process");

View File

@@ -39,6 +39,7 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory)
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
void SetPackedUpdate(FileSys::VirtualFile update_raw);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type);

View File

@@ -468,6 +468,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{80, nullptr, "OpenSaveDataMetaFile"},
{81, nullptr, "OpenSaveDataTransferManager"},
{82, nullptr, "OpenSaveDataTransferManagerVersion2"},
{83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
{100, nullptr, "OpenImageDirectoryFileSystem"},
{110, nullptr, "OpenContentStorageFileSystem"},
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
@@ -495,6 +496,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"},
{614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
{615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
{616, nullptr, "GetSaveDataCommitId"},
{620, nullptr, "SetSdCardEncryptionSeed"},
{630, nullptr, "SetSdCardAccessibility"},
{631, nullptr, "IsSdCardAccessible"},

View File

@@ -21,29 +21,29 @@ public:
{0, nullptr, "Unknown1"},
{1, nullptr, "Unknown2"},
{2, nullptr, "Unknown3"},
{3, nullptr, "Unknown4"},
{4, nullptr, "Unknown5"},
{5, nullptr, "Unknown6"},
{3, nullptr, "GetCurrentBacklightLevel"},
{4, nullptr, "Unknown4"},
{5, nullptr, "GetAlsComputedBacklightLevel"},
{6, nullptr, "TurnOffBacklight"},
{7, nullptr, "TurnOnBacklight"},
{8, nullptr, "GetBacklightStatus"},
{9, nullptr, "Unknown7"},
{10, nullptr, "Unknown8"},
{11, nullptr, "Unknown9"},
{12, nullptr, "Unknown10"},
{13, nullptr, "Unknown11"},
{14, nullptr, "Unknown12"},
{15, nullptr, "Unknown13"},
{9, nullptr, "Unknown5"},
{10, nullptr, "Unknown6"},
{11, nullptr, "Unknown7"},
{12, nullptr, "Unknown8"},
{13, nullptr, "Unknown9"},
{14, nullptr, "Unknown10"},
{15, nullptr, "GetAutoBrightnessSetting"},
{16, nullptr, "ReadRawLightSensor"},
{17, nullptr, "Unknown14"},
{18, nullptr, "Unknown15"},
{19, nullptr, "Unknown16"},
{20, nullptr, "Unknown17"},
{21, nullptr, "Unknown18"},
{22, nullptr, "Unknown19"},
{23, nullptr, "Unknown20"},
{24, nullptr, "Unknown21"},
{25, nullptr, "Unknown22"},
{17, nullptr, "Unknown11"},
{18, nullptr, "Unknown12"},
{19, nullptr, "Unknown13"},
{20, nullptr, "Unknown14"},
{21, nullptr, "Unknown15"},
{22, nullptr, "Unknown16"},
{23, nullptr, "Unknown17"},
{24, nullptr, "Unknown18"},
{25, nullptr, "Unknown19"},
{26, &LBL::EnableVrMode, "EnableVrMode"},
{27, &LBL::DisableVrMode, "DisableVrMode"},
{28, &LBL::GetVrMode, "GetVrMode"},

View File

@@ -157,7 +157,12 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
const auto itr = buffer_mappings.find(params.offset);
ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping");
if (itr == buffer_mappings.end()) {
LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
// Hardware tests shows that unmapping an already unmapped buffer always returns successful
// and doesn't fail.
return 0;
}
auto& system_instance = Core::System::GetInstance();

View File

@@ -6,9 +6,12 @@
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
@@ -48,6 +51,22 @@ public:
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
template <typename T>
std::shared_ptr<T> GetService(const std::string& service_name) const {
static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
"Not a base of ServiceFrameworkBase");
auto service = registered_services.find(service_name);
if (service == registered_services.end()) {
LOG_DEBUG(Service, "Can't find service: {}", service_name);
return nullptr;
}
auto port = service->second->GetServerPort();
if (port == nullptr) {
return nullptr;
}
return std::static_pointer_cast<T>(port->hle_handler);
}
void InvokeControlRequest(Kernel::HLERequestContext& context);
private:

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
#include <cstring>
#include "common/common_funcs.h"
#include "common/file_util.h"
#include "common/logging/log.h"
@@ -130,6 +131,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
}
process.LoadFromMetadata(metadata);
const FileSys::PatchManager pm(metadata.GetTitleID());
// Load NSO modules
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
@@ -139,7 +141,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
const FileSys::VirtualFile module_file = dir->GetFile(module);
if (module_file != nullptr) {
const VAddr load_addr = next_load_addr;
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr);
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr,
std::strcmp(module, "rtld") == 0, pm);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);

View File

@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
constexpr std::array<const char*, 58> RESULT_MESSAGES{
constexpr std::array<const char*, 59> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -152,6 +152,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{
"The BKTR-type NCA has a bad Relocation bucket.",
"The BKTR-type NCA has a bad Subsection bucket.",
"The BKTR-type NCA is missing the base RomFS.",
"The NSP or XCI does not contain an update in addition to the base game.",
};
std::ostream& operator<<(std::ostream& os, ResultStatus status) {

View File

@@ -114,6 +114,7 @@ enum class ResultStatus : u16 {
ErrorBadRelocationBuckets,
ErrorBadSubsectionBuckets,
ErrorMissingBKTRBaseRomFS,
ErrorNoPackedUpdate,
};
std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -196,10 +197,19 @@ public:
/**
* Get the RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
* @param dir The directory containing the RomFS
* @param file The directory containing the RomFS
* @return ResultStatus result of function
*/
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) {
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& file) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the raw update of the application, should it come packed with one
* @param file The raw update NCA file (Program-type
* @return ResultStatus result of function
*/
virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) {
return ResultStatus::ErrorNotImplemented;
}

View File

@@ -72,6 +72,10 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
return nca_loader->ReadRomFS(dir);
}
u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
return nca_loader->ReadRomFSIVFCOffset();
}
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
return nca_loader->ReadProgramId(out_program_id);
}

View File

@@ -36,6 +36,7 @@ public:
ResultStatus Load(Kernel::Process& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
private:

View File

@@ -18,7 +18,9 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nro.h"
#include "core/loader/nso.h"
#include "core/memory.h"
#include "core/settings.h"
namespace Loader {
@@ -150,6 +152,19 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size);
}
if (!Settings::values.program_args.empty()) {
const auto arg_data = Settings::values.program_args;
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
program_image.resize(static_cast<u32>(program_image.size()) +
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
arg_data.size());
}
// Read MOD header
ModHeader mod_header{};
// Default .bss to NRO header bss size if MOD0 section doesn't exist

View File

@@ -10,12 +10,14 @@
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
#include "core/memory.h"
#include "core/settings.h"
namespace Loader {
@@ -36,8 +38,7 @@ struct NsoHeader {
INSERT_PADDING_WORDS(1);
u8 flags;
std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
u32_le bss_size;
INSERT_PADDING_BYTES(0x1c);
std::array<u8, 0x20> build_id;
std::array<u32_le, 3> segments_compressed_size;
bool IsSegmentCompressed(size_t segment_num) const {
@@ -93,7 +94,9 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
bool should_pass_arguments,
boost::optional<FileSys::PatchManager> pm) {
if (file == nullptr)
return {};
@@ -124,6 +127,19 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
}
if (should_pass_arguments && !Settings::values.program_args.empty()) {
const auto arg_data = Settings::values.program_args;
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
program_image.resize(static_cast<u32>(program_image.size()) +
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
arg_data.size());
}
// MOD header pointer is at .text offset + 4
u32 module_offset;
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
@@ -142,6 +158,17 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
program_image.resize(image_size);
// Apply patches if necessary
if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
std::vector<u8> pi_header(program_image.size() + 0x100);
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
pi_header = pm->PatchNSO(pi_header);
std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
}
// Load codeset for current process
codeset->name = file->GetName();
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
@@ -160,7 +187,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
LoadModule(file, base_address);
LoadModule(file, base_address, true);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);

View File

@@ -5,11 +5,21 @@
#pragma once
#include "common/common_types.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace Loader {
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
struct NSOArgumentHeader {
u32_le allocated_size;
u32_le actual_size;
INSERT_PADDING_BYTES(0x18);
};
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
/// Loads an NSO file
class AppLoader_NSO final : public AppLoader, Linker {
public:
@@ -26,7 +36,8 @@ public:
return IdentifyType(file);
}
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base);
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments,
boost::optional<FileSys::PatchManager> pm = boost::none);
ResultStatus Load(Kernel::Process& process) override;
};

View File

@@ -10,8 +10,10 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
#include "core/loader/nsp.h"
@@ -91,13 +93,39 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
if (result != ResultStatus::Success)
return result;
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
is_loaded = true;
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
return secondary_loader->ReadRomFS(dir);
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) {
return secondary_loader->ReadRomFS(file);
}
u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
return secondary_loader->ReadRomFSIVFCOffset();
}
ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& file) {
if (nsp->IsExtractedType())
return ResultStatus::ErrorNoPackedUpdate;
const auto read =
nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program);
if (read == nullptr)
return ResultStatus::ErrorNoPackedUpdate;
const auto nca_test = std::make_shared<FileSys::NCA>(read);
if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
return nca_test->GetStatus();
file = read;
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {

View File

@@ -37,7 +37,9 @@ public:
ResultStatus Load(Kernel::Process& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;

View File

@@ -9,7 +9,11 @@
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/nca.h"
#include "core/loader/xci.h"
@@ -63,13 +67,41 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
if (result != ResultStatus::Success)
return result;
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
is_loaded = true;
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) {
return nca_loader->ReadRomFS(dir);
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) {
return nca_loader->ReadRomFS(file);
}
u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
return nca_loader->ReadRomFSIVFCOffset();
}
ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& file) {
u64 program_id{};
nca_loader->ReadProgramId(program_id);
if (program_id == 0)
return ResultStatus::ErrorXCIMissingProgramNCA;
const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
if (read == nullptr)
return ResultStatus::ErrorNoPackedUpdate;
const auto nca_test = std::make_shared<FileSys::NCA>(read);
if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
return nca_test->GetStatus();
file = read;
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {

View File

@@ -37,7 +37,9 @@ public:
ResultStatus Load(Kernel::Process& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;

View File

@@ -155,6 +155,13 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
std::string program_args;
// WebService
bool enable_telemetry;
std::string web_api_url;
std::string yuzu_username;
std::string yuzu_token;
} extern values;
void Apply();

View File

@@ -6,6 +6,8 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -13,10 +15,31 @@
#include "core/settings.h"
#include "core/telemetry_session.h"
#ifdef ENABLE_WEB_SERVICE
#include "web_service/telemetry_json.h"
#include "web_service/verify_login.h"
#endif
namespace Core {
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_context ctr_drbg;
std::string personalization = "yuzu Telemetry ID";
mbedtls_ctr_drbg_init(&ctr_drbg);
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
reinterpret_cast<const unsigned char*>(personalization.c_str()),
personalization.size()) == 0);
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
sizeof(u64)) == 0);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
return telemetry_id;
}
@@ -25,14 +48,21 @@ u64 GetTelemetryId() {
const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
if (FileUtil::Exists(filename)) {
bool generate_new_id = !FileUtil::Exists(filename);
if (!generate_new_id) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
}
file.ReadBytes(&telemetry_id, sizeof(u64));
} else {
if (telemetry_id == 0) {
LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
generate_new_id = true;
}
}
if (generate_new_id) {
FileUtil::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
@@ -59,23 +89,20 @@ u64 RegenerateTelemetryId() {
return new_telemetry_id;
}
std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) {
bool VerifyLogin(const std::string& username, const std::string& token) {
#ifdef ENABLE_WEB_SERVICE
return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func);
return WebService::VerifyLogin(Settings::values.web_api_url, username, token);
#else
return std::async(std::launch::async, [func{std::move(func)}]() {
func();
return false;
});
return false;
#endif
}
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
if (Settings::values.enable_telemetry) {
backend = std::make_unique<WebService::TelemetryJson>(
Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username,
Settings::values.yuzu_token);
backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
Settings::values.yuzu_username,
Settings::values.yuzu_token);
} else {
backend = std::make_unique<Telemetry::NullVisitor>();
}
@@ -94,7 +121,8 @@ TelemetrySession::TelemetrySession() {
u64 program_id{};
const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
if (res == Loader::ResultStatus::Success) {
AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
std::string name;
System::GetInstance().GetAppLoader().ReadTitle(name);

View File

@@ -4,7 +4,6 @@
#pragma once
#include <future>
#include <memory>
#include "common/telemetry.h"
@@ -31,6 +30,8 @@ public:
field_collection.AddField(type, name, std::move(value));
}
static void FinalizeAsyncJob();
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
@@ -55,6 +56,6 @@ u64 RegenerateTelemetryId();
* @param func A function that gets exectued when the verification is finished
* @returns Future with bool indicating whether the verification succeeded
*/
std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func);
bool VerifyLogin(const std::string& username, const std::string& token);
} // namespace Core

View File

@@ -27,6 +27,8 @@ add_library(video_core STATIC
renderer_base.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
renderer_opengl/gl_primitive_assembler.cpp
renderer_opengl/gl_primitive_assembler.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp

View File

@@ -4,11 +4,13 @@
#include "core/memory.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
: memory_manager(memory_manager), rasterizer{rasterizer} {}
void Fermi2D::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -44,27 +46,31 @@ void Fermi2D::HandleSurfaceCopy() {
u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
if (regs.src.linear == regs.dst.linear) {
// If the input layout and the output layout are the same, just perform a raw copy.
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
Memory::CopyBlock(dest_cpu, source_cpu,
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
return;
}
if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) {
// TODO(bunnei): The below implementation currently will not get hit, as
// AccelerateSurfaceCopy tries to always copy and will always return success. This should be
// changed once we properly support flushing.
u8* src_buffer = Memory::GetPointer(source_cpu);
u8* dst_buffer = Memory::GetPointer(dest_cpu);
if (!regs.src.linear && regs.dst.linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
regs.src.BlockHeight());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
regs.dst.BlockHeight());
if (regs.src.linear == regs.dst.linear) {
// If the input layout and the output layout are the same, just perform a raw copy.
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
Memory::CopyBlock(dest_cpu, source_cpu,
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
return;
}
u8* src_buffer = Memory::GetPointer(source_cpu);
u8* dst_buffer = Memory::GetPointer(dest_cpu);
if (!regs.src.linear && regs.dst.linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
regs.src.BlockHeight());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
regs.dst.BlockHeight());
}
}
}

View File

@@ -12,6 +12,10 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
namespace VideoCore {
class RasterizerInterface;
}
namespace Tegra::Engines {
#define FERMI2D_REG_INDEX(field_name) \
@@ -19,7 +23,7 @@ namespace Tegra::Engines {
class Fermi2D final {
public:
explicit Fermi2D(MemoryManager& memory_manager);
explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
~Fermi2D() = default;
/// Write the value to the register identified by method.
@@ -94,6 +98,8 @@ public:
MemoryManager& memory_manager;
private:
VideoCore::RasterizerInterface& rasterizer;
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
void HandleSurfaceCopy();

View File

@@ -532,7 +532,21 @@ public:
INSERT_PADDING_WORDS(0x3);
s32 clear_stencil;
INSERT_PADDING_WORDS(0x6C);
INSERT_PADDING_WORDS(0x17);
struct {
u32 enable;
union {
BitField<0, 16, u32> min_x;
BitField<16, 16, u32> max_x;
};
union {
BitField<0, 16, u32> min_y;
BitField<16, 16, u32> max_y;
};
} scissor_test;
INSERT_PADDING_WORDS(0x52);
s32 stencil_back_func_ref;
u32 stencil_back_mask;
@@ -744,6 +758,12 @@ public:
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) |
end_addr_low);
}
/// Adjust the index buffer offset so it points to the first desired index.
GPUVAddr IndexStart() const {
return StartAddress() + static_cast<size_t>(first) *
static_cast<size_t>(FormatSizeInBytes());
}
} index_array;
INSERT_PADDING_WORDS(0x7);
@@ -996,6 +1016,7 @@ ASSERT_REG_POSITION(vertex_buffer, 0x35D);
ASSERT_REG_POSITION(clear_color[0], 0x360);
ASSERT_REG_POSITION(clear_depth, 0x364);
ASSERT_REG_POSITION(clear_stencil, 0x368);
ASSERT_REG_POSITION(scissor_test, 0x380);
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);

View File

@@ -25,7 +25,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
memory_manager = std::make_unique<Tegra::MemoryManager>();
maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager);
kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager);

View File

@@ -5,6 +5,7 @@
#pragma once
#include "common/common_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
@@ -33,13 +34,9 @@ public:
/// and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
virtual bool AccelerateDisplayTransfer(const void* config) {
return false;
}
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
virtual bool AccelerateTextureCopy(const void* config) {
/// Attempt to use a faster method to perform a surface copy
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
return false;
}

View File

@@ -34,7 +34,7 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
}
AlignBuffer(alignment);
GLintptr uploaded_offset = buffer_offset;
const GLintptr uploaded_offset = buffer_offset;
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
@@ -57,13 +57,23 @@ GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t s
std::size_t alignment) {
AlignBuffer(alignment);
std::memcpy(buffer_ptr, raw_pointer, size);
GLintptr uploaded_offset = buffer_offset;
const GLintptr uploaded_offset = buffer_offset;
buffer_ptr += size;
buffer_offset += size;
return uploaded_offset;
}
std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::size_t alignment) {
AlignBuffer(alignment);
u8* const uploaded_ptr = buffer_ptr;
const GLintptr uploaded_offset = buffer_offset;
buffer_ptr += size;
buffer_offset += size;
return std::make_tuple(uploaded_ptr, uploaded_offset);
}
void OGLBufferCache::Map(std::size_t max_size) {
bool invalidate;
std::tie(buffer_ptr, buffer_offset_base, invalidate) =
@@ -74,6 +84,7 @@ void OGLBufferCache::Map(std::size_t max_size) {
InvalidateAll();
}
}
void OGLBufferCache::Unmap() {
stream_buffer.Unmap(buffer_offset - buffer_offset_base);
}
@@ -84,7 +95,7 @@ GLuint OGLBufferCache::GetHandle() const {
void OGLBufferCache::AlignBuffer(std::size_t alignment) {
// Align the offset, not the mapped pointer
GLintptr offset_aligned =
const GLintptr offset_aligned =
static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment));
buffer_ptr += offset_aligned - buffer_offset;
buffer_offset = offset_aligned;

View File

@@ -6,6 +6,7 @@
#include <cstddef>
#include <memory>
#include <tuple>
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
@@ -33,11 +34,17 @@ class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBuffer
public:
explicit OGLBufferCache(std::size_t size);
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
/// allocated.
GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
bool cache = true);
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4);
/// Reserves memory to be used by host's CPU. Returns mapped address and offset.
std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4);
void Map(std::size_t max_size);
void Unmap();

View File

@@ -0,0 +1,64 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include "common/assert.h"
#include "common/common_types.h"
#include "core/memory.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
namespace OpenGL {
constexpr u32 TRIANGLES_PER_QUAD = 6;
constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3};
PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {}
PrimitiveAssembler::~PrimitiveAssembler() = default;
std::size_t PrimitiveAssembler::CalculateQuadSize(u32 count) const {
ASSERT_MSG(count % 4 == 0, "Quad count is expected to be a multiple of 4");
return (count / 4) * TRIANGLES_PER_QUAD * sizeof(GLuint);
}
GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
const std::size_t size{CalculateQuadSize(count)};
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(size);
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
for (u32 i = 0; i < TRIANGLES_PER_QUAD; ++i) {
const u32 index = first + primitive * 4 + QUAD_MAP[i];
std::memcpy(dst_pointer, &index, sizeof(index));
dst_pointer += sizeof(index);
}
}
return index_offset;
}
GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
u32 count) {
const std::size_t map_size{CalculateQuadSize(count)};
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
const u8* source{Memory::GetPointer(*cpu_addr)};
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
const u32 index = primitive * 4 + QUAD_MAP[i];
const u8* src_offset = source + (index * index_size);
std::memcpy(dst_pointer, src_offset, index_size);
dst_pointer += index_size;
}
}
return index_offset;
}
} // namespace OpenGL

View File

@@ -0,0 +1,33 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <glad/glad.h>
#include "common/common_types.h"
#include "video_core/memory_manager.h"
namespace OpenGL {
class OGLBufferCache;
class PrimitiveAssembler {
public:
explicit PrimitiveAssembler(OGLBufferCache& buffer_cache);
~PrimitiveAssembler();
/// Calculates the size required by MakeQuadArray and MakeQuadIndexed.
std::size_t CalculateQuadSize(u32 count) const;
GLintptr MakeQuadArray(u32 first, u32 count);
GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
private:
OGLBufferCache& buffer_cache;
};
} // namespace OpenGL

View File

@@ -42,6 +42,41 @@ MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(12
MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
struct DrawParameters {
GLenum primitive_mode;
GLsizei count;
GLint current_instance;
bool use_indexed;
GLint vertex_first;
GLenum index_format;
GLint base_vertex;
GLintptr index_buffer_offset;
void DispatchDraw() const {
if (use_indexed) {
const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
if (current_instance > 0) {
glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
index_buffer_ptr, 1, base_vertex,
current_instance);
} else {
glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
base_vertex);
}
} else {
if (current_instance > 0) {
glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1,
current_instance);
} else {
glDrawArrays(primitive_mode, vertex_first, count);
}
}
}
};
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) {
@@ -172,6 +207,54 @@ void RasterizerOpenGL::SetupVertexArrays() {
}
}
DrawParameters RasterizerOpenGL::SetupDraw() {
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
const auto& regs = gpu.regs;
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
DrawParameters params{};
params.current_instance = gpu.state.current_instance;
if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
MICROPROFILE_SCOPE(OpenGL_PrimitiveAssembly);
params.use_indexed = true;
params.primitive_mode = GL_TRIANGLES;
if (is_indexed) {
params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
params.count = (regs.index_array.count / 4) * 6;
params.index_buffer_offset = primitive_assembler.MakeQuadIndexed(
regs.index_array.IndexStart(), regs.index_array.FormatSizeInBytes(),
regs.index_array.count);
params.base_vertex = static_cast<GLint>(regs.vb_element_base);
} else {
// MakeQuadArray always generates u32 indexes
params.index_format = GL_UNSIGNED_INT;
params.count = (regs.vertex_buffer.count / 4) * 6;
params.index_buffer_offset =
primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count);
}
return params;
}
params.use_indexed = is_indexed;
params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
if (is_indexed) {
MICROPROFILE_SCOPE(OpenGL_Index);
params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
params.count = regs.index_array.count;
params.index_buffer_offset =
buffer_cache.UploadMemory(regs.index_array.IndexStart(), CalculateIndexBufferSize());
params.base_vertex = static_cast<GLint>(regs.vb_element_base);
} else {
params.count = regs.vertex_buffer.count;
params.vertex_first = regs.vertex_buffer.first;
}
return params;
}
void RasterizerOpenGL::SetupShaders() {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -256,6 +339,13 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
return size;
}
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
return static_cast<std::size_t>(regs.index_array.count) *
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
}
bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
DrawArrays();
@@ -451,6 +541,7 @@ void RasterizerOpenGL::DrawArrays() {
SyncLogicOpState();
SyncCullMode();
SyncAlphaTest();
SyncScissorTest();
SyncTransformFeedback();
SyncPointState();
@@ -459,16 +550,23 @@ void RasterizerOpenGL::DrawArrays() {
// Draw the vertex batch
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
static_cast<u64>(regs.index_array.FormatSizeInBytes())};
state.draw.vertex_buffer = buffer_cache.GetHandle();
state.Apply();
std::size_t buffer_size = CalculateVertexArraysSize();
if (is_indexed) {
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + index_buffer_size;
// Add space for index buffer (keeping in mind non-core primitives)
switch (regs.draw.topology) {
case Maxwell::PrimitiveTopology::Quads:
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
primitive_assembler.CalculateQuadSize(regs.vertex_buffer.count);
break;
default:
if (is_indexed) {
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + CalculateIndexBufferSize();
}
break;
}
// Uniform space for the 5 shader stages
@@ -482,20 +580,7 @@ void RasterizerOpenGL::DrawArrays() {
buffer_cache.Map(buffer_size);
SetupVertexArrays();
// If indexed mode, copy the index buffer
GLintptr index_buffer_offset = 0;
if (is_indexed) {
MICROPROFILE_SCOPE(OpenGL_Index);
// Adjust the index buffer offset so it points to the first desired index.
auto index_start = regs.index_array.StartAddress();
index_start += static_cast<size_t>(regs.index_array.first) *
static_cast<size_t>(regs.index_array.FormatSizeInBytes());
index_buffer_offset = buffer_cache.UploadMemory(index_start, index_buffer_size);
}
DrawParameters params = SetupDraw();
SetupShaders();
buffer_cache.Unmap();
@@ -503,31 +588,8 @@ void RasterizerOpenGL::DrawArrays() {
shader_program_manager->ApplyTo(state);
state.Apply();
const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)};
if (is_indexed) {
const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
if (gpu.state.current_instance > 0) {
glDrawElementsInstancedBaseVertexBaseInstance(
primitive_mode, regs.index_array.count,
MaxwellToGL::IndexFormat(regs.index_array.format),
reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
gpu.state.current_instance);
} else {
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
MaxwellToGL::IndexFormat(regs.index_array.format),
reinterpret_cast<const void*>(index_buffer_offset),
base_vertex);
}
} else {
if (gpu.state.current_instance > 0) {
glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
regs.vertex_buffer.count, 1,
gpu.state.current_instance);
} else {
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
}
}
// Execute draw call
params.DispatchDraw();
// Disable scissor test
state.scissor.enabled = false;
@@ -556,14 +618,10 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
InvalidateRegion(addr, size);
}
bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
MICROPROFILE_SCOPE(OpenGL_Blits);
UNREACHABLE();
return true;
}
bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
UNREACHABLE();
res_cache.FermiCopySurface(src, dst);
return true;
}
@@ -601,10 +659,13 @@ void RasterizerOpenGL::SamplerInfo::Create() {
sampler.Create();
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
uses_depth_compare = false;
depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
// default is GL_LINEAR_MIPMAP_LINEAR
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Other attributes have correct defaults
glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
}
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
@@ -632,6 +693,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
}
if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
uses_depth_compare = (config.depth_compare_enabled == 1);
if (uses_depth_compare) {
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
} else {
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
}
}
if (depth_compare_func != config.depth_compare_func) {
depth_compare_func = config.depth_compare_func;
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
MaxwellToGL::DepthCompareFunc(depth_compare_func));
}
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border ||
wrap_p == Tegra::Texture::WrapMode::Border) {
const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g,
@@ -897,6 +973,22 @@ void RasterizerOpenGL::SyncAlphaTest() {
}
}
void RasterizerOpenGL::SyncScissorTest() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
state.scissor.enabled = (regs.scissor_test.enable != 0);
// TODO(Blinkhawk): Figure if the hardware supports scissor testing per viewport and how it's
// implemented.
if (regs.scissor_test.enable != 0) {
const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x;
const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y;
state.scissor.x = regs.scissor_test.min_x;
state.scissor.y = regs.scissor_test.min_y;
state.scissor.width = width;
state.scissor.height = height;
}
}
void RasterizerOpenGL::SyncTransformFeedback() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;

View File

@@ -23,6 +23,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
@@ -38,6 +39,7 @@ class EmuWindow;
namespace OpenGL {
struct ScreenInfo;
struct DrawParameters;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
@@ -50,8 +52,8 @@ public:
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
bool AccelerateDisplayTransfer(const void* config) override;
bool AccelerateTextureCopy(const void* config) override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
const Tegra::Engines::Fermi2D::Regs::Surface& dst) override;
bool AccelerateFill(const void* config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
@@ -94,6 +96,8 @@ private:
Tegra::Texture::WrapMode wrap_u;
Tegra::Texture::WrapMode wrap_v;
Tegra::Texture::WrapMode wrap_p;
bool uses_depth_compare;
Tegra::Texture::DepthCompareFunc depth_compare_func;
GLvec4 border_color;
};
@@ -161,6 +165,9 @@ private:
/// Syncs the alpha test state to match the guest state
void SyncAlphaTest();
/// Syncs the scissor test state to match the guest state
void SyncScissorTest();
/// Syncs the transform feedback state to match the guest state
void SyncTransformFeedback();
@@ -192,12 +199,17 @@ private:
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
OGLBufferCache buffer_cache;
OGLFramebuffer framebuffer;
PrimitiveAssembler primitive_assembler{buffer_cache};
GLint uniform_buffer_alignment;
std::size_t CalculateVertexArraysSize() const;
std::size_t CalculateIndexBufferSize() const;
void SetupVertexArrays();
DrawParameters SetupDraw();
void SetupShaders();
enum class AccelDraw { Disabled, Arrays, Indexed };

View File

@@ -143,6 +143,28 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.Address());
params.is_tiled = !config.linear;
params.block_height = params.is_tiled ? config.BlockHeight() : 0,
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
params.height = config.height;
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
return params;
}
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
{GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
@@ -559,6 +581,18 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
return true;
}
static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) {
const auto& src_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
const u32 width{std::min(src_params.width, dst_params.width)};
const u32 height{std::min(src_params.height, dst_params.height)};
glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0,
0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0,
0, 0, width, height, 1);
}
static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
GLuint copy_pbo_handle, GLenum src_attachment = 0,
GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
@@ -1033,6 +1067,26 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
return surface;
}
void RasterizerCacheOpenGL::FermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) {
const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
ASSERT(src_params.width == dst_params.width);
ASSERT(src_params.height == dst_params.height);
ASSERT(src_params.pixel_format == dst_params.pixel_format);
ASSERT(src_params.block_height == dst_params.block_height);
ASSERT(src_params.is_tiled == dst_params.is_tiled);
ASSERT(src_params.depth == dst_params.depth);
ASSERT(src_params.depth == 1); // Currently, FastCopySurface only works with 2D surfaces
ASSERT(src_params.target == dst_params.target);
ASSERT(src_params.rt.index == dst_params.rt.index);
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
}
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
@@ -1041,6 +1095,15 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// Get a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{GetUncachedSurface(new_params)};
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
if (old_params.target == new_params.target && old_params.type == new_params.type &&
old_params.depth == new_params.depth && old_params.depth == 1 &&
SurfaceParams::GetFormatBpp(old_params.pixel_format) ==
SurfaceParams::GetFormatBpp(new_params.pixel_format)) {
FastCopySurface(old_surface, new_surface);
return new_surface;
}
// If the format is the same, just do a framebuffer blit. This is significantly faster than
// using PBOs. The is also likely less accurate, as textures will be converted rather than
// reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate

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