Compare commits

...

95 Commits

Author SHA1 Message Date
Lioncash
6beb823f15 audio_core: Make g_sink_details internally linked
We can hide the direct array from external view and instead provide
functions to retrieve the necessary info. This has the benefit of
completely hiding the makeup of the SinkDetails structure from the rest
of the code.

Given that this makes the array hidden, we can also make the array
constexpr by altering the members slightly. This gets rid of several
static constructor calls related to std::vector and std::function.

Now we don't have heap allocations here that need to occur before the
program can even enter main(). It also has the benefit of saving a
little bit of heap space, but this doesn't matter too much, since the
savings in that regard are pretty tiny.
2018-12-13 16:44:32 -05:00
bunnei
3b1043c58a Merge pull request #1846 from lioncash/dir
file_sys/directory: Amend path buffer size for directory entries
2018-12-10 21:54:03 -05:00
bunnei
2c45c6d234 Merge pull request #1819 from DarkLordZach/disable-addons
patch_manager: Add support for disabling patches
2018-12-10 21:52:19 -05:00
bunnei
9eb9b344c7 Merge pull request #1887 from FearlessTobi/port-4476
Port citra-emu/citra#4476: "web_service: move telemetry condition from TelemetrySession constructor to destructor"
2018-12-10 21:47:22 -05:00
bunnei
1aa9106244 Merge pull request #1883 from lioncash/log-fsp
service/fsp_srv: Correct returned value in GetGlobalAccessLogMode()
2018-12-10 21:45:29 -05:00
bunnei
01ab4aab91 Merge pull request #1885 from lioncash/data_id
file_sys/save_data_factory: Update SaveDataSpaceId enum
2018-12-10 21:44:50 -05:00
bunnei
5b5d0199fe Merge pull request #1740 from FernandoS27/shader_props
Implemented Shader Unique Identifiers
2018-12-10 12:43:43 -05:00
Hexagon12
315f3342f7 Merge pull request #1872 from lioncash/proc-info
kernel/process: Set ideal core from metadata
2018-12-10 18:44:14 +02:00
Hexagon12
ee9e433517 Merge pull request #1880 from DarkLordZach/cache-storage
savedata_factory: Add CacheStorage and delete TemporaryStorage on boot
2018-12-10 18:41:46 +02:00
bunnei
74242a8fb4 Merge pull request #1876 from lioncash/vma
vm_manager: Make vma_map private
2018-12-10 10:09:50 -05:00
bunnei
be657036be Merge pull request #1862 from marcosvitali/tlds
gl_shader_decompiler: TLDS/TLD4/TLD4S Reworked reflecting the source registers, bugs fixed and modularize.
2018-12-10 10:08:20 -05:00
Lioncash
f3a555a484 service/fsp_srv: Correct returned value in GetGlobalAccessLogMode()
Based off RE, the backing code only ever seems to use 0-2 as the range
of values 1 being a generic log enable, with 2 indicating logging should
go to the SD card. These are used as a set of flags internally.

Given we only care about receiving the log in general, we can just
always signify that we want logging in general.
2018-12-09 20:42:35 -05:00
Fernando Sahmkow
d5d77848e6 Implemented a shader unique identifier. 2018-12-09 17:33:33 -04:00
fearlessTobi
ca4e20b4e0 web_service: move telemetry condition from TelemetrySession constructor to destructor
Fixes an issue where Testcases couldn't be sent when Telemetry was disabled, because both things are tied closely together in the backend.
2018-12-08 14:34:37 +01:00
bunnei
3bddd5351e Merge pull request #1864 from lioncash/nrr
service/ldr: Amend layouts of NRO and NRR headers
2018-12-07 22:26:31 -05:00
Lioncash
a4eaa6782f file_sys/save_data_factory: Update SaveDataSpaceId enum
Amends it with missing values deduced from RE (ProperSystem being from
SwitchBrew for naming)

(SdCardUser wasn't that difficult to discern given it's used alongside
SdCardSystem when creating the save data indexer, based off the usage of
the string "saveDataIxrDbSd" nearby).
2018-12-07 18:52:18 -05:00
bunnei
b9e80e97b7 Merge pull request #1874 from lioncash/bindings
hle/service, hle/sm: Minor cleanup
2018-12-07 18:31:04 -05:00
bunnei
713fc67b51 Merge pull request #1882 from FearlessTobi/backport-4418-fix
Backport review comment from citra-emu/citra#4418
2018-12-07 18:16:10 -05:00
bunnei
f85134021f Merge pull request #1873 from lioncash/const
loaders: Make GetFileType() a const qualified member function
2018-12-07 18:15:30 -05:00
Marcos Vitali
f4fa7ecb0e gl_shader_decompiler: TLDS/TLD4/TLD4S Reworked reflecting the source registers, bugs fixed and modularize. 2018-12-07 19:09:36 -03:00
Tobias
eb15711ee6 Backport review comment from citra-emu/citra#4418
Original reason:
As Windows multi-byte character codec is unspecified while we always assume std::string uses UTF-8 in our code base, this can output gibberish when the string contains non-ASCII characters. ::OutputDebugStringW combined with Common::UTF8ToUTF16W is preferred here.
2018-12-07 16:21:18 +01:00
Zach Hilman
fcfbae88e9 savedata_factory: Add support for CacheStorage 2018-12-07 08:47:32 -05:00
Zach Hilman
5721b8b5ad savedata_factory: Delete TemporaryStorage on startup
Mimics hardware behavior.
2018-12-06 22:07:34 -05:00
bunnei
f761e3ef86 Merge pull request #1868 from lioncash/config
configuration/config: Use an intermediary variable for accessing players
2018-12-06 15:24:28 -05:00
bunnei
30b5d8b0ae Merge pull request #1875 from DarkLordZach/oss-ngword2
system_archive: Implement open source NgWord2
2018-12-06 15:23:58 -05:00
Lioncash
15e3d4f357 memory: Convert ASSERT into a DEBUG_ASSERT within GetPointerFromVMA()
Given memory should always be expected to be valid during normal
execution, this should be a debug assertion, rather than a check in
regular builds.
2018-12-06 15:02:34 -05:00
Lioncash
d4c1b9d311 vm_manager: Make vma_map private
This was only ever public so that code could check whether or not a
handle was valid or not. Instead of exposing the object directly and
allowing external code to potentially mess with the map contents, we
just provide a member function that allows checking whether or not a
handle is valid.

This makes all member variables of the VMManager class private except
for the page table.
2018-12-06 15:02:17 -05:00
bunnei
8de6403a08 Merge pull request #1861 from lioncash/reset
kernel/svc: Correct behavior of svcResetSignal()
2018-12-06 13:42:46 -05:00
bunnei
9390452195 Merge pull request #1824 from ReinUsesLisp/fbcache
gl_rasterizer: Implement a framebuffer cache
2018-12-06 11:56:59 -05:00
bunnei
7fbd484f0e Merge pull request #1863 from ReinUsesLisp/texs-f16
gl_shader_decompiler: Implement TEXS.F16
2018-12-06 11:56:05 -05:00
Zach Hilman
8be475d4dc system_archive: Implement open source NgWord2 2018-12-06 10:17:50 -05:00
Lioncash
24f051d723 hle/service: Replace log + UNIMPLEMENTED with UNIMPLEMENTED_MSG
Combines the two into one, shortening the amount of code here.
2018-12-06 01:40:23 -05:00
Lioncash
9f56477539 hle/service: Remove unnecessary using declarations
Only one usage of the specified objects made use of the lack of
namespacing. Given the low usage, we can just remove these.
2018-12-06 01:37:41 -05:00
Lioncash
d8625f5544 hle/service, hle/sm: Compress usages of MakeResult()
These auto-deduce the result based off its arguments, so there's no need
to do that work for the compiler, plus, the function return value itself
already indicates what we're returning.
2018-12-06 01:33:22 -05:00
Lioncash
a8269fdae3 hle/service, hle/sm: Use structured bindings where applicable
Gets rid of the need to keep the variables separate from their actual
initialization spots.
2018-12-06 01:31:26 -05:00
bunnei
4c106b43a9 Merge pull request #1867 from lioncash/alloc
system_archive: Use a regular function pointer instead of std::function for the file-scope system archive array
2018-12-05 22:37:14 -05:00
bunnei
cca4665a69 Merge pull request #1866 from lioncash/cache
service/ldr: Deduplicate instruction cache clearing code in LoadNro()
2018-12-05 22:36:44 -05:00
Mat M
4d3d2fcebd Merge pull request #1870 from heapo/pagetable_shrink_to_fit
Perf: Call shrink_to_fit after page-table vector resizing to actually reduce vector capacity
2018-12-05 18:36:47 -05:00
Lioncash
01bf329f63 yuzu/game_list_worker: Don't retrieve the file type twice in AddFstEntriesToGameList()
Similarly, here we can avoid doing unnecessary work twice by retrieving
the file type only once and comparing it against relevant operands,
avoiding potential unnecessary object construction/destruction.
2018-12-05 17:58:15 -05:00
Lioncash
de095ded5c yuzu/game_list_worker: Don't retrieve file type and file type strings twice in MakeGameListEntry()
While GetFileType() is indeed a getter function, that doesn't mean it's
a trivial function, given some case require reading from the data or
constructing other objects in the background. Instead, only do necessary
work once.
2018-12-05 17:49:37 -05:00
Lioncash
de323851b4 loaders: Make GetFileType() a const qualified member function
No implementations actually modify instance state (and it would be
questionable to do that in the first place given the name), so we can
make this a const member function.
2018-12-05 17:49:34 -05:00
heapo
ca8ab1bc3b Call shrink_to_fit after page-table vector resizing to cause crt to actually lower vector capacity. For 36-bit titles saves 800MB of commit. 2018-12-05 14:40:56 -08:00
Lioncash
547eecf119 kernel/process: Set ideal core from metadata
A very trivial change. If metadata is available, the process should use
it to retrieve the desired core for the process to run on.
2018-12-05 16:59:37 -05:00
Lioncash
e90fa1ac54 configuration/config: Use an intermediary variable for accessing players
Avoids typing the same long accessor just to retrieve player attributes.
2018-12-05 03:41:33 -05:00
Lioncash
2207baaa04 ng_word: Deduplicate use of a constant value
We've already given the constant to the vector itself, so we don't need
to re-hardcode it in the array.
2018-12-05 02:36:45 -05:00
Lioncash
edd9bfd54a system_archive: Use a regular function pointer instead of std::function for file-scope system archive array
This allows the array to be constexpr. std::function is also allowed to
allocate memory, which makes its constructor non-trivial, we definitely
don't want to have all of these execute at runtime, taking up time
before the application can actually load.
2018-12-05 02:33:17 -05:00
Lioncash
7c9b194d63 service/ldr: Deduplicate instruction cache clearing code in LoadNro()
We don't need to specify all of the ARM interfaces explicitly.
2018-12-05 00:33:47 -05:00
Lioncash
05a6f1f676 service/ldr: Amend layout of the NRO header
The first word is just a padding byte, it's not an actual entry
instruction. Also renames the rest of the entries according to
SwitchBrew.
2018-12-05 00:16:49 -05:00
ReinUsesLisp
59a8df1b14 gl_shader_decompiler: Implement TEXS.F16 2018-12-05 02:06:34 -03:00
Lioncash
817fb18e30 service/ldr: Corrent padding within the NRR header layout
The padding after the magic signature value should be 12 bytes rather
than 28 bytes. The other 16 should be placed after the title ID pattern.
2018-12-05 00:05:04 -05:00
ReinUsesLisp
370980fdc3 gl_shader_decompiler: Fixup inverted if 2018-12-05 01:23:04 -03:00
bunnei
db3200b515 Merge pull request #1859 from heapo/lut_array_codegen
Convert high-frequency LUT arrays from constexpr to static constexpr
2018-12-04 22:07:12 -05:00
Lioncash
2f253986df kernel/svc: Correct behavior of svcResetSignal()
While partially correct, this service call allows the retrieved event to
be null, as it also uses the same handle to check if it was referring to
a Process instance. The previous two changes put the necessary machinery
in place to allow for this, so we can simply call those member functions
here and be done with it.
2018-12-04 20:14:59 -05:00
Lioncash
c7462ce712 kernel/process: Make Process a WaitObject
Process instances can be waited upon for state changes. This is also
utilized by svcResetSignal, which will be modified in an upcoming
change. This simply puts all of the WaitObject related machinery in
place.
2018-12-04 20:14:59 -05:00
Lioncash
a3aa7aaf0b kernel/readable_event: Add member function for enforcing a strict reset contract
svcResetSignal relies on the event instance to have already been
signaled before attempting to reset it. If this isn't the case, then an
error code has to be returned.
2018-12-04 20:14:55 -05:00
heapo
7853e6b5d4 Improve msvc codegen for hot-path array LUTs
In some constexpr functions, msvc is building the LUT at runtime
(pushing each element onto the stack) out of an abundance of caution. Moving the
arrays into be file-scoped constexpr's avoids this and turns the functions into
simple look-ups as intended.
2018-12-04 17:13:07 -08:00
bunnei
d08d4a366b Merge pull request #1704 from DarkLordZach/oss-sysarchive
file_sys: Implement open source system archives
2018-12-04 19:59:52 -05:00
bunnei
af286294f9 Merge pull request #1837 from lioncash/map
yuzu/game_list_worker: Minor cleanup and code deduplication
2018-12-04 19:57:41 -05:00
Lioncash
a49fd7fd57 yuzu/game_list_worker: Move std::string construction after the termination check in callbacks
Avoids potentially allocating a std::string instance when it isn't
needed.
2018-12-04 18:39:35 -05:00
bunnei
e5dd4cb392 Merge pull request #1838 from lioncash/dedup
file_sys/registered_cache: Eliminate variable shadowing
2018-12-04 18:34:49 -05:00
bunnei
173073c722 Merge pull request #1836 from lioncash/unused
crypto/key_manager: Remove unused variable in GetTicketblob()
2018-12-04 18:28:15 -05:00
bunnei
5ca586596d Merge pull request #1860 from lioncash/event
kernel/svc: Implement svcCreateEvent and svcSignalEvent
2018-12-04 18:27:54 -05:00
Lioncash
8ea1f28614 kernel/svc: Remove unused header inclusion 2018-12-04 15:48:20 -05:00
Lioncash
a543c35962 kernel/svc: Implement svcSignalEvent()
This function simply does a handle table lookup for a writable event
instance identified by the given handle value. If a writable event
cannot be found for the given handle, then an invalid handle error is
returned. If a writable event is found, then it simply signals the
event, as one would expect.
2018-12-04 15:47:59 -05:00
Lioncash
2a1f59b301 kernel/svc: Implement svcCreateEvent()
svcCreateEvent operates by creating both a readable and writable event
and then attempts to add both to the current process' handle table.

If adding either of the events to the handle table fails, then the
relevant error from the handle table is returned.

If adding the readable event after the writable event to the table
fails, then the writable event is removed from the handle table and the
relevant error from the handle table is returned.

Note that since we do not currently test resource limits, we don't check
the resource limit table yet.
2018-12-04 15:47:55 -05:00
Zach Hilman
f6f6503578 qt: Add Properties menu to game list right-click 2018-12-04 13:34:50 -05:00
bunnei
465f486160 Merge pull request #1845 from lioncash/nro
loader/{nro, nso}: Remove dependency on the System class
2018-12-04 12:26:12 -05:00
bunnei
d533767623 Merge pull request #1853 from lioncash/event
kernel/object: Amend handle types to distinguish between readable and writable events
2018-12-04 12:25:40 -05:00
Marcos
ab2108fb2a Rewrited TEX/TEXS (TEX Scalar). (#1826)
* Rewrited TEX/TEXS (TEX Scalar).

* Style fixes.

* Styles issues.
2018-12-04 12:24:35 -05:00
bunnei
da5659fb87 Merge pull request #1857 from lioncash/res-info
kernel/svc: Implement the resource limit svcGetInfo option
2018-12-04 12:23:19 -05:00
bunnei
7f6bc284e9 Merge pull request #1854 from Subv/old_command_processor
Don't try to route PFIFO methods (0-0x40) to the other engines.
2018-12-04 08:49:22 -05:00
Lioncash
ac966e4213 kernel/handle_table: Amend reference to CTR-OS in Create()
Another hold-over from Citra.
2018-12-04 01:50:44 -05:00
Lioncash
312690b450 kernel/svc: Implement the resource limit svcGetInfo option
Allows a process to register the resource limit as part of its handle
table.
2018-12-04 01:50:30 -05:00
Subv
c4c19fa6c1 Removed unused file.
This is a leftover from #1792
2018-12-03 23:52:38 -05:00
Subv
b873253da1 GPU: Don't try to route PFIFO methods (0-0x40) to the other engines. 2018-12-03 23:52:18 -05:00
Zach Hilman
60e27252a5 qt: Add UI to display game properties and disable add-ons 2018-12-03 17:21:25 -05:00
Zach Hilman
5f0217592b loader: Add support for reading the name of game's developer 2018-12-03 17:21:25 -05:00
Zach Hilman
51483d83bb aoc_u: Obey disabled add-ons list when listing DLC 2018-12-03 17:21:25 -05:00
Zach Hilman
0cea05cdf7 patch_manager: Obey disabled add-ons list when patching game 2018-12-03 17:21:25 -05:00
Zach Hilman
c7b41ade74 core: Make GetGameFileFromPath function externally accessible 2018-12-03 17:20:34 -05:00
Zach Hilman
c381f46428 config: Store and load disabled add-ons list 2018-12-03 17:20:34 -05:00
Zach Hilman
282b7e902c settings: Store list of disabled add-ons per title ID 2018-12-03 17:20:34 -05:00
Lioncash
7695febfa1 loader/nso: Remove dependency on the System class
Similar to the NRO changes, we can also pass the process explicitly as a
parameter from Load instead of indirecting through the System class.
2018-12-02 23:39:03 -05:00
Lioncash
fc32d6256a loader/nro: Make the static LoadNro function internally linked
This simply acts as a forwarding function for the Load() function, so
this doesn't need to be directly exposed.
2018-12-02 23:38:58 -05:00
Lioncash
7e2467e695 file_sys/directory: Amend path buffer size for directory entries
The path buffer is actually 0x301 (769) characters in length, with the
extra character being intended for the null-terminator.
2018-12-02 23:15:58 -05:00
Lioncash
b110d2176c loader/nro: Remove dependency on the System class
Load() is already given the process instance as a parameter, so instead
of coupling the class to the System class, we can just forward that
parameter to LoadNro()
2018-12-02 22:18:52 -05:00
Lioncash
efbcff0af0 file_sys/registered_cache: Eliminate variable shadowing
Also inverts if statements where applicable to allow unindenting code a
little bit.
2018-12-01 23:50:13 -05:00
Lioncash
8c108eaca7 yuzu/game_list_worker: Deduplicate game list entry creation
Avoids duplicating the same code twice verbatim.
2018-12-01 23:23:39 -05:00
Lioncash
f1ecfcb8bc yuzu/game_list_worker: Tidy up string handling in FillControlMap()
We don't need to call out to our own file handling functions when we're
going to construct a QFileInfo instance right after it. We also don't
need to convert to a std::string again just to compare the file
extension.
2018-12-01 22:49:00 -05:00
Lioncash
a8aca4306d crypto/key_manager: Remove unused variable in GetTicketblob() 2018-12-01 22:37:51 -05:00
ReinUsesLisp
1a2bb596db gl_rasterizer: Implement a framebuffer cache 2018-11-29 16:34:46 -03:00
Zach Hilman
86ad1f8db6 file_sys: Implement system archive synthesizer for NgWord (806) 2018-11-22 21:39:10 -05:00
Zach Hilman
4838bc8ddc fsp_srv: Add support for using open source archive if not found in NAND 2018-11-15 22:35:16 -05:00
Zach Hilman
6aa69880ea file_sys: Add framework for synthesizing open source archives 2018-11-15 22:34:35 -05:00
Zach Hilman
34cd56980f vfs_vector: Add VFS backend for std::array
Allows using constexpr/static const data with VFS.
2018-11-15 22:33:52 -05:00
82 changed files with 2241 additions and 1150 deletions

View File

@@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
Stream::ReleaseCallback&& release_callback) {
if (!sink) {
const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
sink = sink_details.factory(Settings::values.audio_device_id);
sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
}
return std::make_shared<Stream>(

View File

@@ -107,7 +107,7 @@ private:
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
};
CubebSink::CubebSink(std::string target_device_name) {
CubebSink::CubebSink(std::string_view target_device_name) {
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return;

View File

@@ -15,7 +15,7 @@ namespace AudioCore {
class CubebSink final : public Sink {
public:
explicit CubebSink(std::string device_id);
explicit CubebSink(std::string_view device_id);
~CubebSink() override;
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,

View File

@@ -10,7 +10,7 @@ namespace AudioCore {
class NullSink final : public Sink {
public:
explicit NullSink(std::string){};
explicit NullSink(std::string_view) {}
~NullSink() override = default;
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,

View File

@@ -14,31 +14,68 @@
#include "common/logging/log.h"
namespace AudioCore {
namespace {
struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)();
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
const std::vector<SinkDetails> g_sink_details = {
/// Name for this sink.
const char* id;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = {
#ifdef HAVE_CUBEB
SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
SinkDetails{"cubeb",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices},
#endif
SinkDetails{"null", &std::make_unique<NullSink, std::string>,
SinkDetails{"null",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<NullSink>(device_id);
},
[] { return std::vector<std::string>{"null"}; }},
};
const SinkDetails& GetSinkDetails(std::string_view sink_id) {
auto iter =
std::find_if(g_sink_details.begin(), g_sink_details.end(),
std::find_if(std::begin(sink_details), std::end(sink_details),
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
if (sink_id == "auto" || iter == g_sink_details.end()) {
if (sink_id == "auto" || iter == std::end(sink_details)) {
if (sink_id != "auto") {
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
}
// Auto-select.
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
iter = g_sink_details.begin();
// sink_details is ordered in terms of desirability, with the best choice at the front.
iter = std::begin(sink_details);
}
return *iter;
}
} // Anonymous namespace
std::vector<const char*> GetSinkIDs() {
std::vector<const char*> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; });
return sink_ids;
}
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
return GetSinkDetails(sink_id).list_devices();
}
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
return GetSinkDetails(sink_id).factory(device_id);
}
} // namespace AudioCore

View File

@@ -4,34 +4,21 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace AudioCore {
class Sink;
struct SinkDetails {
using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>;
using ListDevicesFn = std::function<std::vector<std::string>()>;
/// Retrieves the IDs for all available audio sinks.
std::vector<const char*> GetSinkIDs();
SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_)
: id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {}
/// Gets the list of devices for a particular sink identified by the given ID.
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
/// Name for this sink.
const char* id;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
};
extern const std::vector<SinkDetails> g_sink_details;
const SinkDetails& GetSinkDetails(std::string_view sink_id);
/// Creates an audio sink identified by the given device ID.
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
} // namespace AudioCore

View File

@@ -13,7 +13,7 @@
#include <vector>
#ifdef _WIN32
#include <share.h> // For _SH_DENYWR
#include <windows.h> // For OutputDebugStringA
#include <windows.h> // For OutputDebugStringW
#else
#define _SH_DENYWR 0
#endif
@@ -148,7 +148,7 @@ void FileBackend::Write(const Entry& entry) {
void DebuggerBackend::Write(const Entry& entry) {
#ifdef _WIN32
::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
}

View File

@@ -63,6 +63,10 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
file_sys/system_archive/ng_word.cpp
file_sys/system_archive/ng_word.h
file_sys/system_archive/system_archive.cpp
file_sys/system_archive/system_archive.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp

View File

@@ -8,6 +8,7 @@
#include <thread>
#include <utility>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
@@ -40,7 +41,6 @@ namespace Core {
/*static*/ System System::s_instance;
namespace {
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path) {
// To account for split 00+01+etc files.
@@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
}
if (FileUtil::IsDirectory(path))
return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
return vfs->OpenFile(path, FileSys::Mode::Read);
}
} // Anonymous namespace
struct System::Impl {
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
}

View File

@@ -9,6 +9,7 @@
#include <string>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
namespace Core::Frontend {
@@ -55,6 +56,9 @@ class TelemetrySession;
struct PerfStatsResults;
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path);
class System {
public:
System(const System&) = delete;

View File

@@ -246,7 +246,6 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
}
std::vector<TicketRaw> out;
u32 magic{};
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {

View File

@@ -29,8 +29,8 @@ struct Entry {
filename[copy_size] = '\0';
}
char filename[0x300];
INSERT_PADDING_BYTES(4);
char filename[0x301];
INSERT_PADDING_BYTES(3);
EntryType type;
INSERT_PADDING_BYTES(3);
u64 file_size;

View File

@@ -56,6 +56,10 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
u64 PatchManager::GetTitleID() const {
return title_id;
}
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
@@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
const auto installed = Service::FileSystem::GetUnionContents();
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
if (update != nullptr && update->GetExeFS() != nullptr &&
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
@@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
std::vector<VirtualDir> layers;
layers.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr)
layers.push_back(std::move(exefs_dir));
@@ -111,11 +122,16 @@ 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> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id];
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
@@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
const auto& disabled = Settings::values.disabled_addons[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(); });
@@ -237,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
@@ -282,7 +302,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntryRaw(update_tid, type);
if (update != nullptr) {
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
if (!update_disabled && update != nullptr) {
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -290,7 +315,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (update_raw != nullptr) {
} else if (!update_disabled && 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) {
@@ -320,25 +345,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid};
auto [nacp, discard_icon_file] = update.GetControlMetadata();
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
const auto update_label = update_disabled ? "[D] Update" : "Update";
if (nacp != nullptr) {
out.insert_or_assign("Update", nacp->GetVersionString());
out.insert_or_assign(update_label, nacp->GetVersionString());
} else {
if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign("Update", "");
out.insert_or_assign(update_label, "");
} else {
out.insert_or_assign(
"Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
out.insert_or_assign("Update", "PACKED");
out.insert_or_assign(update_label, "PACKED");
}
}
@@ -378,7 +408,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (types.empty())
continue;
out.insert_or_assign(mod->GetName(), types);
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
}
}
@@ -401,7 +433,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
out.insert_or_assign("DLC", std::move(list));
const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
}
return out;

View File

@@ -30,6 +30,8 @@ public:
explicit PatchManager(u64 title_id);
~PatchManager();
u64 GetTitleID() const;
// Currently tracked ExeFS patches:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
@@ -63,6 +65,9 @@ public:
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
u64 title_id;
};

View File

@@ -107,42 +107,41 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
const auto file = dir->GetFileRelative(path);
if (file != nullptr)
return file;
const auto nca_dir = dir->GetDirectoryRelative(path);
if (nca_dir != nullptr) {
const auto nca_dir = dir->GetDirectoryRelative(path);
VirtualFile file = nullptr;
const auto files = nca_dir->GetFiles();
if (files.size() == 1 && files[0]->GetName() == "00") {
file = files[0];
} else {
std::vector<VirtualFile> concat;
// Since the files are a two-digit hex number, max is FF.
for (std::size_t i = 0; i < 0x100; ++i) {
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr) {
concat.push_back(std::move(next));
} else {
next = nca_dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else
break;
}
}
if (concat.empty())
return nullptr;
file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
}
if (file != nullptr) {
return file;
}
return nullptr;
const auto nca_dir = dir->GetDirectoryRelative(path);
if (nca_dir == nullptr) {
return nullptr;
}
const auto files = nca_dir->GetFiles();
if (files.size() == 1 && files[0]->GetName() == "00") {
return files[0];
}
std::vector<VirtualFile> concat;
// Since the files are a two-digit hex number, max is FF.
for (std::size_t i = 0; i < 0x100; ++i) {
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr) {
concat.push_back(std::move(next));
} else {
next = nca_dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr) {
concat.push_back(std::move(next));
} else {
break;
}
}
}
if (concat.empty()) {
return nullptr;
}
return ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
}
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {

View File

@@ -18,7 +18,11 @@ std::string SaveDataDescriptor::DebugInfo() const {
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
}
SaveDataFactory::~SaveDataFactory() = default;
@@ -120,6 +124,8 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::TemporaryStorage:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::CacheStorage:
return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
}

View File

@@ -17,8 +17,10 @@ namespace FileSys {
enum class SaveDataSpaceId : u8 {
NandSystem = 0,
NandUser = 1,
SdCard = 2,
SdCardSystem = 2,
TemporaryStorage = 3,
SdCardUser = 4,
ProperSystem = 100,
};
enum class SaveDataType : u8 {

View File

@@ -0,0 +1,81 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/common_types.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys::SystemArchive {
namespace NgWord1Data {
constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10;
// Should this archive replacement mysteriously not work on a future game, consider updating.
constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x19}; // 5.1.0 System Version
constexpr std::array<u8, 30> WORD_TXT{
0xFE, 0xFF, 0x00, 0x5E, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x62, 0x00,
0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0A,
}; // "^verybadword$" in UTF-16
} // namespace NgWord1Data
VirtualDir NgWord1() {
std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES);
for (std::size_t i = 0; i < files.size(); ++i) {
files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
NgWord1Data::WORD_TXT, fmt::format("{}.txt", i));
}
files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
NgWord1Data::WORD_TXT, "common.txt"));
files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>(
NgWord1Data::VERSION_DAT, "version.dat"));
return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
}
namespace NgWord2Data {
constexpr std::size_t NUMBER_AC_NX_FILES = 0x10;
// Should this archive replacement mysteriously not work on a future game, consider updating.
constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x15}; // 5.1.0 System Version
constexpr std::array<u8, 0x2C> AC_NX_DATA{
0x1F, 0x8B, 0x08, 0x08, 0xD5, 0x2C, 0x09, 0x5C, 0x04, 0x00, 0x61, 0x63, 0x72, 0x61, 0x77,
0x00, 0xED, 0xC1, 0x01, 0x0D, 0x00, 0x00, 0x00, 0xC2, 0x20, 0xFB, 0xA7, 0xB6, 0xC7, 0x07,
0x0C, 0x00, 0x00, 0x00, 0xC8, 0x3B, 0x11, 0x00, 0x1C, 0xC7, 0x00, 0x10, 0x00, 0x00,
}; // Deserializes to no bad words
} // namespace NgWord2Data
VirtualDir NgWord2() {
std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3);
for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) {
files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i));
files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i));
files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i));
}
files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>(
NgWord2Data::VERSION_DAT, "version.dat"));
return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
}
} // namespace FileSys::SystemArchive

View File

@@ -0,0 +1,14 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/file_sys/vfs_types.h"
namespace FileSys::SystemArchive {
VirtualDir NgWord1();
VirtualDir NgWord2();
} // namespace FileSys::SystemArchive

View File

@@ -0,0 +1,90 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/system_archive/system_archive.h"
namespace FileSys::SystemArchive {
constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
using SystemArchiveSupplier = VirtualDir (*)();
struct SystemArchiveDescriptor {
u64 title_id;
const char* name;
SystemArchiveSupplier supplier;
};
constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
{0x0100000000000800, "CertStore", nullptr},
{0x0100000000000801, "ErrorMessage", nullptr},
{0x0100000000000802, "MiiModel", nullptr},
{0x0100000000000803, "BrowserDll", nullptr},
{0x0100000000000804, "Help", nullptr},
{0x0100000000000805, "SharedFont", nullptr},
{0x0100000000000806, "NgWord", &NgWord1},
{0x0100000000000807, "SsidList", nullptr},
{0x0100000000000808, "Dictionary", nullptr},
{0x0100000000000809, "SystemVersion", nullptr},
{0x010000000000080A, "AvatarImage", nullptr},
{0x010000000000080B, "LocalNews", nullptr},
{0x010000000000080C, "Eula", nullptr},
{0x010000000000080D, "UrlBlackList", nullptr},
{0x010000000000080E, "TimeZoneBinary", nullptr},
{0x010000000000080F, "CertStoreCruiser", nullptr},
{0x0100000000000810, "FontNintendoExtension", nullptr},
{0x0100000000000811, "FontStandard", nullptr},
{0x0100000000000812, "FontKorean", nullptr},
{0x0100000000000813, "FontChineseTraditional", nullptr},
{0x0100000000000814, "FontChineseSimple", nullptr},
{0x0100000000000815, "FontBfcpx", nullptr},
{0x0100000000000816, "SystemUpdate", nullptr},
{0x0100000000000817, "0100000000000817", nullptr},
{0x0100000000000818, "FirmwareDebugSettings", nullptr},
{0x0100000000000819, "BootImagePackage", nullptr},
{0x010000000000081A, "BootImagePackageSafe", nullptr},
{0x010000000000081B, "BootImagePackageExFat", nullptr},
{0x010000000000081C, "BootImagePackageExFatSafe", nullptr},
{0x010000000000081D, "FatalMessage", nullptr},
{0x010000000000081E, "ControllerIcon", nullptr},
{0x010000000000081F, "PlatformConfigIcosa", nullptr},
{0x0100000000000820, "PlatformConfigCopper", nullptr},
{0x0100000000000821, "PlatformConfigHoag", nullptr},
{0x0100000000000822, "ControllerFirmware", nullptr},
{0x0100000000000823, "NgWord2", &NgWord2},
{0x0100000000000824, "PlatformConfigIcosaMariko", nullptr},
{0x0100000000000825, "ApplicationBlackList", nullptr},
{0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr},
{0x0100000000000827, "ContentActionTable", nullptr},
}};
VirtualFile SynthesizeSystemArchive(const u64 title_id) {
if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id)
return nullptr;
const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID];
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id);
if (desc.supplier == nullptr)
return nullptr;
const auto dir = desc.supplier();
if (dir == nullptr)
return nullptr;
const auto romfs = CreateRomFS(dir);
if (romfs == nullptr)
return nullptr;
LOG_INFO(Service_FS, " - System archive generation successful!");
return romfs;
}
} // namespace FileSys::SystemArchive

View File

@@ -0,0 +1,14 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
namespace FileSys::SystemArchive {
VirtualFile SynthesizeSystemArchive(u64 title_id);
} // namespace FileSys::SystemArchive

View File

@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <utility>
#include "core/file_sys/vfs_vector.h"

View File

@@ -4,10 +4,63 @@
#pragma once
#include <cstring>
#include "core/file_sys/vfs.h"
namespace FileSys {
// An implementation of VfsFile that is backed by a statically-sized array
template <std::size_t size>
class ArrayVfsFile : public VfsFile {
public:
ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr)
: data(data), name(std::move(name)), parent(std::move(parent)) {}
std::string GetName() const override {
return name;
}
std::size_t GetSize() const override {
return size;
}
bool Resize(std::size_t new_size) override {
return false;
}
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
return parent;
}
bool IsWritable() const override {
return false;
}
bool IsReadable() const override {
return true;
}
std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
const auto read = std::min(length, size - offset);
std::memcpy(data_, data.data() + offset, read);
return read;
}
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
return 0;
}
bool Rename(std::string_view name) override {
this->name = name;
return true;
}
private:
std::array<u8, size> data;
std::string name;
VirtualDir parent;
};
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:

View File

@@ -42,9 +42,10 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
u16 generation = next_generation++;
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
// CTR-OS doesn't use generation 0, so skip straight to 1.
if (next_generation >= (1 << 15))
// Horizon OS uses zero to represent an invalid handle, so skip to 1.
if (next_generation >= (1 << 15)) {
next_generation = 1;
}
generations[slot] = generation;
objects[slot] = std::move(obj);

View File

@@ -13,6 +13,7 @@
namespace Kernel {
enum KernelHandle : Handle {
InvalidHandle = 0,
CurrentThread = 0xFFFF8000,
CurrentProcess = 0xFFFF8001,
};

View File

@@ -15,6 +15,7 @@ bool Object::IsWaitable() const {
switch (GetHandleType()) {
case HandleType::ReadableEvent:
case HandleType::Thread:
case HandleType::Process:
case HandleType::Timer:
case HandleType::ServerPort:
case HandleType::ServerSession:
@@ -23,7 +24,6 @@ bool Object::IsWaitable() const {
case HandleType::Unknown:
case HandleType::WritableEvent:
case HandleType::SharedMemory:
case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
case HandleType::ClientPort:

View File

@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
@@ -44,8 +45,28 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
return process;
}
SharedPtr<ResourceLimit> Process::GetResourceLimit() const {
return resource_limit;
}
ResultCode Process::ClearSignalState() {
if (status == ProcessStatus::Exited) {
LOG_ERROR(Kernel, "called on a terminated process instance.");
return ERR_INVALID_STATE;
}
if (!is_signaled) {
LOG_ERROR(Kernel, "called on a process instance that isn't signaled.");
return ERR_INVALID_STATE;
}
is_signaled = false;
return RESULT_SUCCESS;
}
void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
ideal_processor = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
}
@@ -133,13 +154,13 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
.Unwrap();
vm_manager.LogLayout();
status = ProcessStatus::Running;
ChangeStatus(ProcessStatus::Running);
Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this);
}
void Process::PrepareForTermination() {
status = ProcessStatus::Exited;
ChangeStatus(ProcessStatus::Exiting);
const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
for (auto& thread : thread_list) {
@@ -163,6 +184,8 @@ void Process::PrepareForTermination() {
stop_threads(system.Scheduler(1).GetThreadList());
stop_threads(system.Scheduler(2).GetThreadList());
stop_threads(system.Scheduler(3).GetThreadList());
ChangeStatus(ProcessStatus::Exited);
}
/**
@@ -261,7 +284,25 @@ ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) {
return vm_manager.UnmapRange(dst_addr, size);
}
Kernel::Process::Process(KernelCore& kernel) : Object{kernel} {}
Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {}
Kernel::Process::~Process() {}
void Process::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
}
bool Process::ShouldWait(Thread* thread) const {
return !is_signaled;
}
void Process::ChangeStatus(ProcessStatus new_status) {
if (status == new_status) {
return;
}
status = new_status;
is_signaled = true;
WakeupAllWaitingThreads();
}
} // namespace Kernel

View File

@@ -14,9 +14,10 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace FileSys {
class ProgramMetadata;
@@ -117,7 +118,7 @@ struct CodeSet final {
VAddr entrypoint = 0;
};
class Process final : public Object {
class Process final : public WaitObject {
public:
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
@@ -171,14 +172,7 @@ public:
}
/// Gets the resource limit descriptor for this process
ResourceLimit& GetResourceLimit() {
return *resource_limit;
}
/// Gets the resource limit descriptor for this process
const ResourceLimit& GetResourceLimit() const {
return *resource_limit;
}
SharedPtr<ResourceLimit> GetResourceLimit() const;
/// Gets the default CPU ID for this process
u8 GetDefaultProcessorID() const {
@@ -219,6 +213,16 @@ public:
return random_entropy.at(index);
}
/// Clears the signaled state of the process if and only if it's signaled.
///
/// @pre The process must not be already terminated. If this is called on a
/// terminated process, then ERR_INVALID_STATE will be returned.
///
/// @pre The process must be in a signaled state. If this is called on a
/// process instance that is not signaled, ERR_INVALID_STATE will be
/// returned.
ResultCode ClearSignalState();
/**
* Loads process-specifics configuration info with metadata provided
* by an executable.
@@ -267,6 +271,17 @@ private:
explicit Process(KernelCore& kernel);
~Process() override;
/// Checks if the specified thread should wait until this process is available.
bool ShouldWait(Thread* thread) const override;
/// Acquires/locks this process for the specified thread if it's available.
void Acquire(Thread* thread) override;
/// Changes the process status. If the status is different
/// from the current process status, then this will trigger
/// a process signal.
void ChangeStatus(ProcessStatus new_status);
/// Memory manager for this process.
Kernel::VMManager vm_manager;
@@ -312,6 +327,10 @@ private:
/// specified by metadata provided to the process during loading.
bool is_64bit_process = true;
/// Whether or not this process is signaled. This occurs
/// upon the process changing to a different state.
bool is_signaled = false;
/// Total running time for the process in ticks.
u64 total_process_running_time_ticks = 0;

View File

@@ -4,10 +4,10 @@
#include <algorithm>
#include "common/assert.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
namespace Kernel {
@@ -34,6 +34,16 @@ void ReadableEvent::Clear() {
signaled = false;
}
ResultCode ReadableEvent::Reset() {
if (!signaled) {
return ERR_INVALID_STATE;
}
Clear();
return RESULT_SUCCESS;
}
void ReadableEvent::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();

View File

@@ -7,6 +7,8 @@
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
union ResultCode;
namespace Kernel {
class KernelCore;
@@ -39,8 +41,17 @@ public:
void WakeupAllWaitingThreads() override;
/// Unconditionally clears the readable event's state.
void Clear();
/// Clears the readable event's state if and only if it
/// has already been signaled.
///
/// @pre The event must be in a signaled state. If this event
/// is in an unsignaled state and this function is called,
/// then ERR_INVALID_STATE will be returned.
ResultCode Reset();
private:
explicit ReadableEvent(KernelCore& kernel);

View File

@@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce
shared_memory->backing_block.get());
}
} else {
auto& vm_manager = shared_memory->owner_process->VMManager();
const auto& vm_manager = shared_memory->owner_process->VMManager();
// The memory is already available and mapped in the owner process.
auto vma = vm_manager.FindVMA(address);
ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address");
const auto vma = vm_manager.FindVMA(address);
ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address");
ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
// The returned VMA might be a bigger one encompassing the desired address.
auto vma_offset = address - vma->first;
const auto vma_offset = address - vma->first;
ASSERT_MSG(vma_offset + size <= vma->second.size,
"Shared memory exceeds bounds of mapped block");

View File

@@ -35,7 +35,6 @@
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "core/settings.h"
namespace Kernel {
namespace {
@@ -240,7 +239,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
}
const VMManager::VMAHandle iter = vm_manager.FindVMA(addr);
if (iter == vm_manager.vma_map.end()) {
if (!vm_manager.IsValidHandle(iter)) {
LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr);
return ERR_INVALID_ADDRESS_STATE;
}
@@ -663,7 +662,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
TotalMemoryUsage = 6,
TotalHeapUsage = 7,
IsCurrentProcessBeingDebugged = 8,
ResourceHandleLimit = 9,
RegisterResourceLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
@@ -787,6 +786,33 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
*result = 0;
return RESULT_SUCCESS;
case GetInfoType::RegisterResourceLimit: {
if (handle != 0) {
return ERR_INVALID_HANDLE;
}
if (info_sub_id != 0) {
return ERR_INVALID_COMBINATION;
}
Process* const current_process = Core::CurrentProcess();
HandleTable& handle_table = current_process->GetHandleTable();
const auto resource_limit = current_process->GetResourceLimit();
if (!resource_limit) {
*result = KernelHandle::InvalidHandle;
// Yes, the kernel considers this a successful operation.
return RESULT_SUCCESS;
}
const auto table_result = handle_table.Create(resource_limit);
if (table_result.Failed()) {
return table_result.Code();
}
*result = *table_result;
return RESULT_SUCCESS;
}
case GetInfoType::RandomEntropy:
if (handle != 0) {
LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}",
@@ -1051,19 +1077,23 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
process_handle);
return ERR_INVALID_HANDLE;
}
auto vma = process->VMManager().FindVMA(addr);
const auto& vm_manager = process->VMManager();
const auto vma = vm_manager.FindVMA(addr);
memory_info->attributes = 0;
if (vma == process->VMManager().vma_map.end()) {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
memory_info->type = static_cast<u32>(MemoryState::Unmapped);
} else {
if (vm_manager.IsValidHandle(vma)) {
memory_info->base_address = vma->second.base;
memory_info->permission = static_cast<u32>(vma->second.permissions);
memory_info->size = vma->second.size;
memory_info->type = static_cast<u32>(vma->second.meminfo_state);
} else {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
memory_info->type = static_cast<u32>(MemoryState::Unmapped);
}
return RESULT_SUCCESS;
}
@@ -1407,17 +1437,24 @@ static ResultCode CloseHandle(Handle handle) {
return handle_table.Close(handle);
}
/// Reset an event
/// Clears the signaled state of an event or process.
static ResultCode ResetSignal(Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
auto event = handle_table.Get<ReadableEvent>(handle);
if (event) {
return event->Reset();
}
ASSERT(event != nullptr);
auto process = handle_table.Get<Process>(handle);
if (process) {
return process->ClearSignalState();
}
event->Clear();
return RESULT_SUCCESS;
LOG_ERROR(Kernel_SVC, "Invalid handle (0x{:08X})", handle);
return ERR_INVALID_HANDLE;
}
/// Creates a TransferMemory object
@@ -1571,6 +1608,34 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
return RESULT_SUCCESS;
}
static ResultCode CreateEvent(Handle* write_handle, Handle* read_handle) {
LOG_DEBUG(Kernel_SVC, "called");
auto& kernel = Core::System::GetInstance().Kernel();
const auto [readable_event, writable_event] =
WritableEvent::CreateEventPair(kernel, ResetType::Sticky, "CreateEvent");
HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
const auto write_create_result = handle_table.Create(writable_event);
if (write_create_result.Failed()) {
return write_create_result.Code();
}
*write_handle = *write_create_result;
const auto read_create_result = handle_table.Create(readable_event);
if (read_create_result.Failed()) {
handle_table.Close(*write_create_result);
return read_create_result.Code();
}
*read_handle = *read_create_result;
LOG_DEBUG(Kernel_SVC,
"successful. Writable event handle=0x{:08X}, Readable event handle=0x{:08X}",
*write_create_result, *read_create_result);
return RESULT_SUCCESS;
}
static ResultCode ClearEvent(Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
@@ -1592,6 +1657,21 @@ static ResultCode ClearEvent(Handle handle) {
return ERR_INVALID_HANDLE;
}
static ResultCode SignalEvent(Handle handle) {
LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
HandleTable& handle_table = Core::CurrentProcess()->GetHandleTable();
auto writable_event = handle_table.Get<WritableEvent>(handle);
if (!writable_event) {
LOG_ERROR(Kernel_SVC, "Non-existent writable event handle used (0x{:08X})", handle);
return ERR_INVALID_HANDLE;
}
writable_event->Signal();
return RESULT_SUCCESS;
}
static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
@@ -1727,7 +1807,7 @@ static const FunctionDef SVC_Table[] = {
{0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"},
{0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"},
{0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"},
{0x11, nullptr, "SignalEvent"},
{0x11, SvcWrap<SignalEvent>, "SignalEvent"},
{0x12, SvcWrap<ClearEvent>, "ClearEvent"},
{0x13, SvcWrap<MapSharedMemory>, "MapSharedMemory"},
{0x14, SvcWrap<UnmapSharedMemory>, "UnmapSharedMemory"},
@@ -1779,7 +1859,7 @@ static const FunctionDef SVC_Table[] = {
{0x42, nullptr, "ReplyAndReceiveLight"},
{0x43, nullptr, "ReplyAndReceive"},
{0x44, nullptr, "ReplyAndReceiveWithUserBuffer"},
{0x45, nullptr, "CreateEvent"},
{0x45, SvcWrap<CreateEvent>, "CreateEvent"},
{0x46, nullptr, "Unknown"},
{0x47, nullptr, "Unknown"},
{0x48, nullptr, "MapPhysicalMemoryUnsafe"},

View File

@@ -59,6 +59,19 @@ void SvcWrap() {
FuncReturn(retval);
}
template <ResultCode func(u32*, u32*)>
void SvcWrap() {
u32 param_1 = 0;
u32 param_2 = 0;
const u32 retval = func(&param_1, &param_2).raw;
auto& arm_interface = Core::CurrentArmInterface();
arm_interface.SetReg(1, param_1);
arm_interface.SetReg(2, param_2);
FuncReturn(retval);
}
template <ResultCode func(u32*, u64)>
void SvcWrap() {
u32 param_1 = 0;

View File

@@ -87,6 +87,10 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
}
}
bool VMManager::IsValidHandle(VMAHandle handle) const {
return handle != vma_map.cend();
}
ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
std::shared_ptr<std::vector<u8>> block,
std::size_t offset, u64 size,

View File

@@ -113,16 +113,10 @@ struct VirtualMemoryArea {
* - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
*/
class VMManager final {
using VMAMap = std::map<VAddr, VirtualMemoryArea>;
public:
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
* `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
* merged when possible so that no two similar and adjacent regions exist that have not been
* merged.
*/
std::map<VAddr, VirtualMemoryArea> vma_map;
using VMAHandle = decltype(vma_map)::const_iterator;
using VMAHandle = VMAMap::const_iterator;
VMManager();
~VMManager();
@@ -133,6 +127,9 @@ public:
/// Finds the VMA in which the given address is included in, or `vma_map.end()`.
VMAHandle FindVMA(VAddr target) const;
/// Indicates whether or not the given handle is within the VMA map.
bool IsValidHandle(VMAHandle handle) const;
// TODO(yuriks): Should these functions actually return the handle?
/**
@@ -281,7 +278,7 @@ public:
Memory::PageTable page_table;
private:
using VMAIter = decltype(vma_map)::iterator;
using VMAIter = VMAMap::iterator;
/// Converts a VMAHandle to a mutable VMAIter.
VMAIter StripIterConstness(const VMAHandle& iter);
@@ -328,6 +325,15 @@ private:
/// Clears out the page table
void ClearPageTable();
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
* `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
* merged when possible so that no two similar and adjacent regions exist that have not been
* merged.
*/
VMAMap vma_map;
u32 address_space_width = 0;
VAddr address_space_base = 0;
VAddr address_space_end = 0;

View File

@@ -20,6 +20,7 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Service::AOC {
@@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
rb.Push<u32>(0);
return;
}
rb.Push<u32>(static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
@@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
}
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
out = {};
if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find the correct error code.

View File

@@ -20,6 +20,7 @@
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
@@ -795,9 +796,18 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called");
enum class LogMode : u32 {
Off,
Log,
RedirectToSdCard,
LogToSdCard = Log | RedirectToSdCard,
};
// Given we always want to receive logging information,
// we always specify logging as enabled.
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(5);
rb.PushEnum(LogMode::Log);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
@@ -831,6 +841,15 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (data.Failed()) {
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
if (archive != nullptr) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface(std::make_shared<IStorage>(archive));
return;
}
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
"could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,

View File

@@ -335,10 +335,7 @@ public:
vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
Kernel::VMAPermission::ReadWrite);
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
Core::System::GetInstance().InvalidateCpuInstructionCaches();
nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size});
@@ -391,10 +388,7 @@ public:
Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS);
ASSERT(process->UnmapMemory(mapped_addr, 0, nro_size) == RESULT_SUCCESS);
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
Core::System::GetInstance().InvalidateCpuInstructionCaches();
nro.erase(iter);
IPC::ResponseBuilder rb{ctx, 2};
@@ -414,13 +408,13 @@ private:
using SHA256Hash = std::array<u8, 0x20>;
struct NROHeader {
u32_le entrypoint_insn;
INSERT_PADDING_WORDS(1);
u32_le mod_offset;
INSERT_PADDING_WORDS(2);
u32_le magic;
INSERT_PADDING_WORDS(1);
u32_le version;
u32_le nro_size;
INSERT_PADDING_WORDS(1);
u32_le flags;
u32_le text_offset;
u32_le text_size;
u32_le ro_offset;
@@ -436,9 +430,10 @@ private:
struct NRRHeader {
u32_le magic;
INSERT_PADDING_BYTES(0x1C);
INSERT_PADDING_BYTES(12);
u64_le title_id_mask;
u64_le title_id_pattern;
INSERT_PADDING_BYTES(16);
std::array<u8, 0x100> modulus;
std::array<u8, 0x100> signature_1;
std::array<u8, 0x100> signature_2;

View File

@@ -70,10 +70,6 @@
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/wlan/wlan.h"
using Kernel::ClientPort;
using Kernel::ServerPort;
using Kernel::SharedPtr;
namespace Service {
/**
@@ -110,10 +106,8 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
ASSERT(port == nullptr);
auto& kernel = Core::System::GetInstance().Kernel();
SharedPtr<ServerPort> server_port;
SharedPtr<ClientPort> client_port;
std::tie(server_port, client_port) =
ServerPort::CreatePortPair(kernel, max_sessions, service_name);
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
kernel.AddNamedPort(service_name, std::move(client_port));
}
@@ -122,11 +116,9 @@ Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
ASSERT(port == nullptr);
auto& kernel = Core::System::GetInstance().Kernel();
Kernel::SharedPtr<Kernel::ServerPort> server_port;
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) =
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap();
port = MakeResult(std::move(server_port)).Unwrap();
port->SetHleHandler(shared_from_this());
return client_port;
}
@@ -152,8 +144,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
LOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf));
UNIMPLEMENTED();
UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
}
void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {

View File

@@ -54,13 +54,11 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
return ERR_ALREADY_REGISTERED;
auto& kernel = Core::System::GetInstance().Kernel();
Kernel::SharedPtr<Kernel::ServerPort> server_port;
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) =
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
registered_services.emplace(std::move(name), std::move(client_port));
return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
return MakeResult(std::move(server_port));
}
ResultCode ServiceManager::UnregisterService(const std::string& name) {
@@ -83,7 +81,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort(
return ERR_SERVICE_NOT_REGISTERED;
}
return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second);
return MakeResult(it->second);
}
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService(

View File

@@ -7,7 +7,6 @@
#include "common/common_funcs.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -146,7 +145,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
const VAddr load_addr = next_load_addr;
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr =
AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm);
AppLoader_NSO::LoadModule(process, *module_file, load_addr, should_pass_arguments, pm);
if (!tentative_next_load_addr) {
return ResultStatus::ErrorLoadingNSO;
}

View File

@@ -33,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}

View File

@@ -22,7 +22,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}

View File

@@ -12,6 +12,7 @@
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
namespace Kernel {
@@ -131,7 +132,7 @@ public:
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
virtual FileType GetFileType() = 0;
virtual FileType GetFileType() const = 0;
/**
* Load the application and return the created Process instance
@@ -243,6 +244,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the developer of the application
* @param developer Reference to store the application developer into
* @return ResultStatus result of function
*/
virtual ResultStatus ReadDeveloper(std::string& developer) {
return ResultStatus::ErrorNotImplemented;
}
protected:
FileSys::VirtualFile file;
bool is_loaded = false;

View File

@@ -37,7 +37,7 @@ FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {
return IdentifyTypeImpl(nax);
}
FileType AppLoader_NAX::GetFileType() {
FileType AppLoader_NAX::GetFileType() const {
return IdentifyTypeImpl(*nax);
}

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override;
FileType GetFileType() const override;
ResultStatus Load(Kernel::Process& process) override;

View File

@@ -29,7 +29,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}

View File

@@ -10,7 +10,6 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_offset.h"
@@ -129,9 +128,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name,
VAddr load_base) {
static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
const std::string& name, VAddr load_base) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -189,7 +187,7 @@ static constexpr u32 PageAlignSize(u32 size) {
// Load codeset for current process
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(name, load_base, load_base);
@@ -197,8 +195,9 @@ static constexpr u32 PageAlignSize(u32 size) {
return true;
}
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base);
bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file,
VAddr load_base) {
return LoadNroImpl(process, file.ReadAllBytes(), file.GetName(), load_base);
}
ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
@@ -209,7 +208,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
// Load NRO
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadNro(*file, base_address)) {
if (!LoadNro(process, *file, base_address)) {
return ResultStatus::ErrorLoadingNRO;
}

View File

@@ -14,6 +14,10 @@ namespace FileSys {
class NACP;
}
namespace Kernel {
class Process;
}
namespace Loader {
/// Loads an NRO file
@@ -29,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -41,10 +45,8 @@ public:
ResultStatus ReadTitle(std::string& title) override;
bool IsRomFSUpdatable() const override;
static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base);
private:
bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
bool LoadNro(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;

View File

@@ -9,7 +9,6 @@
#include "common/file_util.h"
#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/process.h"
@@ -93,7 +92,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base,
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm) {
if (file.GetSize() < sizeof(NsoHeader))
@@ -166,7 +166,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAd
// Load codeset for current process
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
@@ -181,7 +181,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadModule(*file, base_address, true)) {
if (!LoadModule(process, *file, base_address, true)) {
return ResultStatus::ErrorLoadingNSO;
}
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);

View File

@@ -10,6 +10,10 @@
#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace Kernel {
class Process;
}
namespace Loader {
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
@@ -33,12 +37,12 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}
static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file,
VAddr load_base, bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm = {});
ResultStatus Load(Kernel::Process& process) override;

View File

@@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) {
if (nacp_file == nullptr)
return ResultStatus::ErrorNoControl;
developer = nacp_file->GetDeveloperName();
return ResultStatus::Success;
}
} // namespace Loader

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -43,6 +43,7 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::NSP> nsp;

View File

@@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) {
if (nacp_file == nullptr)
return ResultStatus::ErrorNoControl;
developer = nacp_file->GetDeveloperName();
return ResultStatus::Success;
}
} // namespace Loader

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -43,6 +43,7 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::XCI> xci;

View File

@@ -53,6 +53,14 @@ void PageTable::Resize(std::size_t address_space_width_in_bits) {
pointers.resize(num_page_table_entries);
attributes.resize(num_page_table_entries);
// The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
// vector size is subsequently decreased (via resize), the vector might not automatically
// actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
// 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
pointers.shrink_to_fit();
attributes.shrink_to_fit();
}
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
@@ -117,14 +125,13 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin
* using a VMA from the current process
*/
static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
const auto& vm_manager = process.VMManager();
const auto it = vm_manager.FindVMA(vaddr);
DEBUG_ASSERT(vm_manager.IsValidHandle(it));
u8* direct_pointer = nullptr;
auto& vm_manager = process.VMManager();
auto it = vm_manager.FindVMA(vaddr);
ASSERT(it != vm_manager.vma_map.end());
auto& vma = it->second;
const auto& vma = it->second;
switch (vma.type) {
case Kernel::VMAType::AllocatedMemoryBlock:
direct_pointer = vma.backing_block->data() + vma.offset;

View File

@@ -6,8 +6,10 @@
#include <array>
#include <atomic>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "common/common_types.h"
namespace Settings {
@@ -411,6 +413,9 @@ struct Values {
std::string web_api_url;
std::string yuzu_username;
std::string yuzu_token;
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
} extern values;
void Apply();

View File

@@ -103,13 +103,8 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
if (Settings::values.enable_telemetry) {
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>();
}
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>();
#endif
@@ -180,7 +175,8 @@ TelemetrySession::~TelemetrySession() {
// This is just a placeholder to wrap up the session once the core completes and this is
// destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
field_collection.Accept(*backend);
backend->Complete();
if (Settings::values.enable_telemetry)
backend->Complete();
backend = nullptr;
}

View File

@@ -1,142 +0,0 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstddef>
#include <memory>
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/vector_math.h"
#include "core/memory.h"
#include "core/tracer/recorder.h"
#include "video_core/command_processor.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_compute.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
namespace Tegra {
enum class BufferMethods {
BindObject = 0,
CountBufferMethods = 0x40,
};
MICROPROFILE_DEFINE(ProcessCommandLists, "GPU", "Execute command buffer", MP_RGB(128, 128, 192));
void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) {
MICROPROFILE_SCOPE(ProcessCommandLists);
// On entering GPU code, assume all memory may be touched by the ARM core.
maxwell_3d->dirty_flags.OnMemoryWrite();
auto WriteReg = [this](u32 method, u32 subchannel, u32 value, u32 remaining_params) {
LOG_TRACE(HW_GPU,
"Processing method {:08X} on subchannel {} value "
"{:08X} remaining params {}",
method, subchannel, value, remaining_params);
ASSERT(subchannel < bound_engines.size());
if (method == static_cast<u32>(BufferMethods::BindObject)) {
// Bind the current subchannel to the desired engine id.
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value);
bound_engines[subchannel] = static_cast<EngineID>(value);
return;
}
if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) {
// TODO(Subv): Research and implement these methods.
LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
return;
}
const EngineID engine = bound_engines[subchannel];
switch (engine) {
case EngineID::FERMI_TWOD_A:
fermi_2d->WriteReg(method, value);
break;
case EngineID::MAXWELL_B:
maxwell_3d->WriteReg(method, value, remaining_params);
break;
case EngineID::MAXWELL_COMPUTE_B:
maxwell_compute->WriteReg(method, value);
break;
case EngineID::MAXWELL_DMA_COPY_A:
maxwell_dma->WriteReg(method, value);
break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
kepler_memory->WriteReg(method, value);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented engine");
}
};
for (auto entry : commands) {
Tegra::GPUVAddr address = entry.Address();
u32 size = entry.sz;
const std::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
VAddr current_addr = *head_address;
while (current_addr < *head_address + size * sizeof(CommandHeader)) {
const CommandHeader header = {Memory::Read32(current_addr)};
current_addr += sizeof(u32);
switch (header.mode.Value()) {
case SubmissionMode::IncreasingOld:
case SubmissionMode::Increasing: {
// Increase the method value with each argument.
for (unsigned i = 0; i < header.arg_count; ++i) {
WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr),
header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
}
case SubmissionMode::NonIncreasingOld:
case SubmissionMode::NonIncreasing: {
// Use the same method value for all arguments.
for (unsigned i = 0; i < header.arg_count; ++i) {
WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
}
case SubmissionMode::IncreaseOnce: {
ASSERT(header.arg_count.Value() >= 1);
// Use the original method for the first argument and then the next method for all
// other arguments.
WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
header.arg_count - 1);
current_addr += sizeof(u32);
for (unsigned i = 1; i < header.arg_count; ++i) {
WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr),
header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
}
case SubmissionMode::Inline: {
// The register value is stored in the bits 16-28 as an immediate
WriteReg(header.method, header.subchannel, header.inline_data, 0);
break;
}
default:
UNIMPLEMENTED();
}
}
}
}
} // namespace Tegra

View File

@@ -1049,6 +1049,7 @@ union Instruction {
BitField<49, 1, u64> nodep_flag;
BitField<50, 3, u64> component_mask_selector;
BitField<53, 4, u64> texture_info;
BitField<60, 1, u64> fp32_flag;
TextureType GetTextureType() const {
// The TEXS instruction has a weird encoding for the texture type.
@@ -1549,7 +1550,7 @@ private:
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"),
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
INST("1101-00---------", Id::TEXS, Type::Memory, "TEXS"),
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"),
INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"),

View File

@@ -141,6 +141,12 @@ void GPU::CallMethod(const MethodCall& method_call) {
return;
}
if (method_call.method < static_cast<u32>(BufferMethods::CountBufferMethods)) {
// TODO(Subv): Research and implement these methods.
LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
return;
}
const EngineID engine = bound_engines[method_call.subchannel];
switch (engine) {

View File

@@ -79,6 +79,26 @@ struct DrawParameters {
}
};
struct FramebufferCacheKey {
bool is_single_buffer = false;
bool stencil_enable = false;
std::array<GLenum, Maxwell::NumRenderTargets> color_attachments{};
std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors{};
u32 colors_count = 0;
GLuint zeta = 0;
auto Tie() const {
return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count,
zeta);
}
bool operator<(const FramebufferCacheKey& rhs) const {
return Tie() < rhs.Tie();
}
};
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info},
buffer_cache(*this, STREAM_BUFFER_SIZE) {
@@ -90,9 +110,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
OpenGLState::ApplyDefaultState();
// Create render framebuffer
framebuffer.Create();
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
state.draw.shader_program = 0;
state.Apply();
@@ -361,6 +378,44 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
SyncClipEnabled(clip_distances);
}
void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
OpenGLState& current_state) {
const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey);
auto& framebuffer = entry->second;
if (is_cache_miss)
framebuffer.Create();
current_state.draw.draw_framebuffer = framebuffer.handle;
current_state.ApplyFramebufferState();
if (!is_cache_miss)
return;
if (fbkey.is_single_buffer) {
if (fbkey.color_attachments[0] != GL_NONE) {
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0],
0);
}
glDrawBuffer(fbkey.color_attachments[0]);
} else {
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
if (fbkey.colors[index]) {
glFramebufferTexture(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
fbkey.colors[index], 0);
}
}
glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data());
}
if (fbkey.zeta) {
GLenum zeta_attachment =
fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0);
}
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
@@ -444,10 +499,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
UNIMPLEMENTED_IF(regs.rt_separate_frag_data != 0);
// Bind the framebuffer surfaces
current_state.draw.draw_framebuffer = framebuffer.handle;
current_state.ApplyFramebufferState();
current_state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0;
FramebufferCacheKey fbkey;
if (using_color_fb) {
if (single_color_target) {
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
@@ -463,14 +518,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
}
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
color_surface != nullptr ? color_surface->Texture().handle : 0, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target));
fbkey.is_single_buffer = true;
fbkey.color_attachments[0] =
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target);
fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0;
} else {
// Multiple color attachments are enabled
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
@@ -485,22 +538,17 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
color_surface->GetSurfaceParams().srgb_conversion;
}
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
GL_TEXTURE_2D, color_surface != nullptr ? color_surface->Texture().handle : 0,
0);
fbkey.color_attachments[index] =
GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
fbkey.colors[index] =
color_surface != nullptr ? color_surface->Texture().handle : 0;
}
glDrawBuffers(regs.rt_control.count, buffers.data());
fbkey.is_single_buffer = false;
fbkey.colors_count = regs.rt_control.count;
}
} else {
// No color attachments are enabled - zero out all of them
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), GL_TEXTURE_2D,
0, 0);
}
glDrawBuffer(GL_NONE);
// No color attachments are enabled - leave them as zero
fbkey.is_single_buffer = true;
}
if (depth_surface) {
@@ -508,22 +556,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
// the shader doesn't actually write to it.
depth_surface->MarkAsModified(true, res_cache);
if (regs.stencil_enable) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
depth_surface->Texture().handle, 0);
} else {
// Attach depth
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
depth_surface->Texture().handle, 0);
// Clear stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
}
} else {
// Clear both depth and stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
fbkey.zeta = depth_surface->Texture().handle;
fbkey.stencil_enable = regs.stencil_enable;
}
SetupCachedFramebuffer(fbkey, current_state);
SyncViewport(current_state);
}

View File

@@ -40,6 +40,7 @@ namespace OpenGL {
struct ScreenInfo;
struct DrawParameters;
struct FramebufferCacheKey;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
@@ -195,11 +196,12 @@ private:
OGLVertexArray>
vertex_array_cache;
std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache;
std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
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;
@@ -214,6 +216,8 @@ private:
void SetupShaders(GLenum primitive_mode);
void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state);
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;

View File

@@ -2,7 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/functional/hash.hpp>
#include "common/assert.h"
#include "common/hash.h"
#include "core/core.h"
#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
@@ -66,14 +68,17 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
// stage here.
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
case Maxwell::ShaderProgram::VertexB:
CalculateProperties();
program_result = GLShader::GenerateVertexShader(setup);
gl_type = GL_VERTEX_SHADER;
break;
case Maxwell::ShaderProgram::Geometry:
CalculateProperties();
program_result = GLShader::GenerateGeometryShader(setup);
gl_type = GL_GEOMETRY_SHADER;
break;
case Maxwell::ShaderProgram::Fragment:
CalculateProperties();
program_result = GLShader::GenerateFragmentShader(setup);
gl_type = GL_FRAGMENT_SHADER;
break;
@@ -140,6 +145,46 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
return target_program.handle;
};
static bool IsSchedInstruction(u32 offset, u32 main_offset) {
// sched instructions appear once every 4 instructions.
static constexpr std::size_t SchedPeriod = 4;
const std::size_t absolute_offset = offset - main_offset;
return (absolute_offset % SchedPeriod) == 0;
}
static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
const std::size_t start_offset = 10;
std::size_t offset = start_offset;
std::size_t size = start_offset * sizeof(u64);
while (offset < program.size()) {
const u64 inst = program[offset];
if (!IsSchedInstruction(offset, start_offset)) {
if (inst == 0 || (inst >> 52) == 0x50b) {
break;
}
}
size += 8;
offset++;
}
return size;
}
void CachedShader::CalculateProperties() {
setup.program.real_size = CalculateProgramSize(setup.program.code);
setup.program.real_size_b = 0;
setup.program.unique_identifier = Common::CityHash64(
reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size);
if (program_type == Maxwell::ShaderProgram::VertexA) {
std::size_t seed = 0;
boost::hash_combine(seed, setup.program.unique_identifier);
setup.program.real_size_b = CalculateProgramSize(setup.program.code_b);
const u64 identifier_b = Common::CityHash64(
reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b);
boost::hash_combine(seed, identifier_b);
setup.program.unique_identifier = static_cast<u64>(seed);
}
}
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {

View File

@@ -81,6 +81,8 @@ private:
GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
u32 max_vertices, const std::string& debug_name);
void CalculateProperties();
VAddr addr;
std::size_t shader_length;
Maxwell::ShaderProgram program_type;

View File

@@ -50,6 +50,14 @@ public:
using std::runtime_error::runtime_error;
};
/// Generates code to use for a swizzle operation.
static std::string GetSwizzle(u64 elem) {
ASSERT(elem <= 3);
std::string swizzle = ".";
swizzle += "xyzw"[elem];
return swizzle;
}
/// Translate topology
static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
switch (topology) {
@@ -1004,14 +1012,6 @@ private:
}
}
/// Generates code to use for a swizzle operation.
static std::string GetSwizzle(u64 elem) {
ASSERT(elem <= 3);
std::string swizzle = ".";
swizzle += "xyzw"[elem];
return swizzle;
}
ShaderWriter& shader;
ShaderWriter& declarations;
std::vector<GLSLRegister> regs;
@@ -1343,7 +1343,7 @@ private:
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
}
void WriteTexsInstruction(const Instruction& instr, const std::string& texture) {
void WriteTexsInstructionFloat(const Instruction& instr, const std::string& texture) {
// TEXS has two destination registers and a swizzle. The first two elements in the swizzle
// go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
@@ -1368,6 +1368,38 @@ private:
}
}
void WriteTexsInstructionHalfFloat(const Instruction& instr, const std::string& texture) {
// TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
// float instruction).
std::array<std::string, 4> components;
u32 written_components = 0;
for (u32 component = 0; component < 4; ++component) {
if (!instr.texs.IsComponentEnabled(component))
continue;
components[written_components++] = texture + GetSwizzle(component);
}
if (written_components == 0)
return;
const auto BuildComponent = [&](std::string low, std::string high, bool high_enabled) {
return "vec2(" + low + ", " + (high_enabled ? high : "0") + ')';
};
regs.SetRegisterToHalfFloat(
instr.gpr0, 0, BuildComponent(components[0], components[1], written_components > 1),
Tegra::Shader::HalfMerge::H0_H1, 1, 1);
if (written_components > 2) {
ASSERT(instr.texs.HasTwoDestinations());
regs.SetRegisterToHalfFloat(
instr.gpr28, 0,
BuildComponent(components[2], components[3], written_components > 3),
Tegra::Shader::HalfMerge::H0_H1, 1, 1);
}
}
static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) {
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D:
@@ -1515,6 +1547,254 @@ private:
}
}
std::pair<size_t, std::string> ValidateAndGetCoordinateElement(
const Tegra::Shader::TextureType texture_type, const bool depth_compare,
const bool is_array, const bool lod_bias_enabled, size_t max_coords, size_t max_inputs) {
const size_t coord_count = TextureCoordinates(texture_type);
size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
const size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
if (total_coord_count > max_coords || total_reg_count > max_inputs) {
UNIMPLEMENTED_MSG("Unsupported Texture operation");
total_coord_count = std::min(total_coord_count, max_coords);
}
// 1D.DC opengl is using a vec3 but 2nd component is ignored later.
total_coord_count +=
(depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D)
? 1
: 0;
constexpr std::array<const char*, 5> coord_container{
{"", "float coord = (", "vec2 coord = vec2(", "vec3 coord = vec3(",
"vec4 coord = vec4("}};
return std::pair<size_t, std::string>(coord_count, coord_container[total_coord_count]);
}
std::string GetTextureCode(const Tegra::Shader::Instruction& instr,
const Tegra::Shader::TextureType texture_type,
const Tegra::Shader::TextureProcessMode process_mode,
const bool depth_compare, const bool is_array,
const size_t bias_offset) {
if ((texture_type == Tegra::Shader::TextureType::Texture3D &&
(is_array || depth_compare)) ||
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array &&
depth_compare)) {
UNIMPLEMENTED_MSG("This method is not supported.");
}
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
const bool lod_needed = process_mode == Tegra::Shader::TextureProcessMode::LZ ||
process_mode == Tegra::Shader::TextureProcessMode::LL ||
process_mode == Tegra::Shader::TextureProcessMode::LLA;
const bool gl_lod_supported = !(
(texture_type == Tegra::Shader::TextureType::Texture2D && is_array && depth_compare) ||
(texture_type == Tegra::Shader::TextureType::TextureCube && !is_array &&
depth_compare));
const std::string read_method = lod_needed && gl_lod_supported ? "textureLod(" : "texture(";
std::string texture = read_method + sampler + ", coord";
if (process_mode != Tegra::Shader::TextureProcessMode::None) {
if (process_mode == Tegra::Shader::TextureProcessMode::LZ) {
if (gl_lod_supported) {
texture += ", 0";
} else {
// Lod 0 is emulated by a big negative bias
// in scenarios that are not supported by glsl
texture += ", -1000";
}
} else {
// If present, lod or bias are always stored in the register indexed by the
// gpr20
// field with an offset depending on the usage of the other registers
texture += ',' + regs.GetRegisterAsFloat(instr.gpr20.Value() + bias_offset);
}
}
texture += ")";
return texture;
}
std::pair<std::string, std::string> GetTEXCode(
const Instruction& instr, const Tegra::Shader::TextureType texture_type,
const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare,
const bool is_array) {
const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None &&
process_mode != Tegra::Shader::TextureProcessMode::LZ);
const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement(
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
const u64 coord_register = array_register + (is_array ? 1 : 0);
std::string coord = coord_dcl;
for (size_t i = 0; i < coord_count;) {
coord += regs.GetRegisterAsFloat(coord_register + i);
++i;
if (i != coord_count) {
coord += ',';
}
}
// 1D.DC in opengl the 2nd component is ignored.
if (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) {
coord += ",0.0";
}
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
// or in the next register if lod or bias are used
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
coord += ',' + regs.GetRegisterAsFloat(depth_register);
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
return std::make_pair(
coord, GetTextureCode(instr, texture_type, process_mode, depth_compare, is_array, 0));
}
std::pair<std::string, std::string> GetTEXSCode(
const Instruction& instr, const Tegra::Shader::TextureType texture_type,
const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare,
const bool is_array) {
const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None &&
process_mode != Tegra::Shader::TextureProcessMode::LZ);
const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement(
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
const u64 coord_register = array_register + (is_array ? 1 : 0);
const u64 last_coord_register =
(is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
? static_cast<u64>(instr.gpr20.Value())
: coord_register + 1;
std::string coord = coord_dcl;
for (size_t i = 0; i < coord_count; ++i) {
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i);
if (i < coord_count - 1) {
coord += ',';
}
}
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
// or in the next register if lod or bias are used
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
coord += ',' + regs.GetRegisterAsFloat(depth_register);
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
return std::make_pair(coord,
GetTextureCode(instr, texture_type, process_mode, depth_compare,
is_array, (coord_count > 2 ? 1 : 0)));
}
std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr,
const Tegra::Shader::TextureType texture_type,
const bool depth_compare, const bool is_array) {
const size_t coord_count = TextureCoordinates(texture_type);
const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
constexpr std::array<const char*, 5> coord_container{
{"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}};
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
const u64 coord_register = array_register + (is_array ? 1 : 0);
std::string coord = coord_container[total_coord_count];
for (size_t i = 0; i < coord_count;) {
coord += regs.GetRegisterAsFloat(coord_register + i);
++i;
if (i != coord_count) {
coord += ',';
}
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
std::string texture = "textureGather(" + sampler + ", coord, ";
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')';
} else {
texture += std::to_string(instr.tld4.component) + ')';
}
return std::make_pair(coord, texture);
}
std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr,
const Tegra::Shader::TextureType texture_type,
const bool is_array) {
const size_t coord_count = TextureCoordinates(texture_type);
const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
const bool lod_enabled =
instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL;
constexpr std::array<const char*, 4> coord_container{
{"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}};
std::string coord = coord_container[total_coord_count];
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// if is array gpr20 is used
const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
const u64 last_coord_register =
((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array
? static_cast<u64>(instr.gpr20.Value())
: coord_register + 1;
for (size_t i = 0; i < coord_count; ++i) {
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i);
if (i < coord_count - 1) {
coord += ',';
}
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false);
std::string texture = "texelFetch(" + sampler + ", coords";
if (lod_enabled) {
// When lod is used always is in grp20
texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')';
} else {
texture += ", 0)";
}
return std::make_pair(coord, texture);
}
/**
* Compiles a single instruction from Tegra to GLSL.
* @param offset the offset of the Tegra shader instruction.
@@ -2574,168 +2854,32 @@ private:
case OpCode::Id::TEX: {
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
const bool is_array = instr.tex.array != 0;
const bool depth_compare =
instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const auto process_mode = instr.tex.GetTextureProcessMode();
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
"AOFFI is not implemented");
const bool depth_compare =
instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
u32 num_coordinates = TextureCoordinates(texture_type);
u32 start_index = 0;
std::string array_elem;
if (is_array) {
array_elem = regs.GetRegisterAsInteger(instr.gpr8);
start_index = 1;
}
const auto process_mode = instr.tex.GetTextureProcessMode();
u32 start_index_b = 0;
std::string lod_value;
if (process_mode != Tegra::Shader::TextureProcessMode::LZ &&
process_mode != Tegra::Shader::TextureProcessMode::None) {
start_index_b = 1;
lod_value = regs.GetRegisterAsFloat(instr.gpr20);
}
std::string depth_value;
if (depth_compare) {
depth_value = regs.GetRegisterAsFloat(instr.gpr20.Value() + start_index_b);
}
bool depth_compare_extra = false;
const auto [coord, texture] =
GetTEXCode(instr, texture_type, process_mode, depth_compare, is_array);
const auto scope = shader.Scope();
switch (num_coordinates) {
case 1: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
if (is_array) {
if (depth_compare) {
shader.AddLine("vec3 coords = vec3(" + x + ", " + depth_value + ", " +
array_elem + ");");
} else {
shader.AddLine("vec2 coords = vec2(" + x + ", " + array_elem + ");");
}
} else {
if (depth_compare) {
shader.AddLine("vec2 coords = vec2(" + x + ", " + depth_value + ");");
} else {
shader.AddLine("float coords = " + x + ';');
}
}
break;
}
case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
const std::string y =
regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 1);
if (is_array) {
if (depth_compare) {
shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " +
depth_value + ", " + array_elem + ");");
} else {
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " +
array_elem + ");");
}
} else {
if (depth_compare) {
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " +
depth_value + ");");
} else {
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
}
}
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
const std::string y =
regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 1);
const std::string z =
regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 2);
if (is_array) {
depth_compare_extra = depth_compare;
shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
array_elem + ");");
} else {
if (depth_compare) {
shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
depth_value + ");");
} else {
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
}
}
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
static_cast<u32>(num_coordinates));
// Fallback to interpreting as a 2D texture for now
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
texture_type = Tegra::Shader::TextureType::Texture2D;
}
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
const std::string texture = [&]() {
switch (instr.tex.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::None:
if (depth_compare_extra) {
return "texture(" + sampler + ", coords, " + depth_value + ')';
}
return "texture(" + sampler + ", coords)";
case Tegra::Shader::TextureProcessMode::LZ:
if (depth_compare_extra) {
return "texture(" + sampler + ", coords, " + depth_value + ')';
}
return "textureLod(" + sampler + ", coords, 0.0)";
case Tegra::Shader::TextureProcessMode::LB:
case Tegra::Shader::TextureProcessMode::LBA:
// TODO: Figure if A suffix changes the equation at all.
if (depth_compare_extra) {
LOG_WARNING(
HW_GPU,
"OpenGL Limitation: can't set bias value along depth compare");
return "texture(" + sampler + ", coords, " + depth_value + ')';
}
return "texture(" + sampler + ", coords, " + lod_value + ')';
case Tegra::Shader::TextureProcessMode::LL:
case Tegra::Shader::TextureProcessMode::LLA:
// TODO: Figure if A suffix changes the equation at all.
if (depth_compare_extra) {
LOG_WARNING(
HW_GPU,
"OpenGL Limitation: can't set lod value along depth compare");
return "texture(" + sampler + ", coords, " + depth_value + ')';
}
return "textureLod(" + sampler + ", coords, " + lod_value + ')';
default:
UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
static_cast<u32>(instr.tex.GetTextureProcessMode()));
if (depth_compare_extra) {
return "texture(" + sampler + ", coords, " + depth_value + ')';
}
return "texture(" + sampler + ", coords)";
}
}();
shader.AddLine(coord);
if (depth_compare) {
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
} else {
shader.AddLine("vec4 texture_tmp = " + texture + ';');
std::size_t dest_elem{};
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false,
dest_elem);
++dest_elem;
}
}
@@ -2743,138 +2887,37 @@ private:
}
case OpCode::Id::TEXS: {
Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()};
bool is_array{instr.texs.IsArrayTexture()};
const bool is_array{instr.texs.IsArrayTexture()};
const bool depth_compare =
instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const auto process_mode = instr.texs.GetTextureProcessMode();
UNIMPLEMENTED_IF_MSG(instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
const auto scope = shader.Scope();
const bool depth_compare =
instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
u32 num_coordinates = TextureCoordinates(texture_type);
const auto process_mode = instr.texs.GetTextureProcessMode();
u32 lod_offset = 0;
if (process_mode == Tegra::Shader::TextureProcessMode::LL) {
if (num_coordinates > 2) {
shader.AddLine("float lod_value = " +
regs.GetRegisterAsFloat(instr.gpr20.Value() + 1) + ';');
lod_offset = 2;
} else {
shader.AddLine("float lod_value = " + regs.GetRegisterAsFloat(instr.gpr20) +
';');
lod_offset = 1;
}
}
auto [coord, texture] =
GetTEXSCode(instr, texture_type, process_mode, depth_compare, is_array);
switch (num_coordinates) {
case 1: {
shader.AddLine("float coords = " + regs.GetRegisterAsFloat(instr.gpr8) + ';');
break;
}
case 2: {
if (is_array) {
if (depth_compare) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
index + ");");
} else {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + index +
");");
}
} else {
if (lod_offset != 0) {
if (depth_compare) {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y =
regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z =
regs.GetRegisterAsFloat(instr.gpr20.Value() + lod_offset);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z +
");");
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y =
regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
}
} else {
if (depth_compare) {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y =
regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z +
");");
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
}
}
}
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
static_cast<u32>(num_coordinates));
shader.AddLine(coord);
// Fallback to interpreting as a 2D texture for now
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
texture_type = Tegra::Shader::TextureType::Texture2D;
is_array = false;
}
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
std::string texture = [&]() {
switch (process_mode) {
case Tegra::Shader::TextureProcessMode::None:
return "texture(" + sampler + ", coords)";
case Tegra::Shader::TextureProcessMode::LZ:
if (depth_compare && is_array) {
return "texture(" + sampler + ", coords)";
} else {
return "textureLod(" + sampler + ", coords, 0.0)";
}
break;
case Tegra::Shader::TextureProcessMode::LL:
return "textureLod(" + sampler + ", coords, lod_value)";
default:
UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
static_cast<u32>(instr.texs.GetTextureProcessMode()));
return "texture(" + sampler + ", coords)";
}
}();
if (depth_compare) {
texture = "vec4(" + texture + ')';
}
shader.AddLine("vec4 texture_tmp = " + texture + ';');
WriteTexsInstruction(instr, texture);
if (instr.texs.fp32_flag) {
WriteTexsInstructionFloat(instr, "texture_tmp");
} else {
WriteTexsInstructionHalfFloat(instr, "texture_tmp");
}
break;
}
case OpCode::Id::TLDS: {
const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
const bool is_array{instr.tlds.IsArrayTexture()};
ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D);
ASSERT(is_array == false);
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
@@ -2882,54 +2925,16 @@ private:
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
u32 extra_op_offset = 0;
const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array);
ShaderScopedScope scope = shader.Scope();
const auto scope = shader.Scope();
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
shader.AddLine("float coords = " + x + ';');
break;
}
case Tegra::Shader::TextureType::Texture2D: {
UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture");
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
// shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
extra_op_offset = 1;
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
}
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, false);
const std::string texture = [&]() {
switch (instr.tlds.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::LZ:
return "texelFetch(" + sampler + ", coords, 0)";
case Tegra::Shader::TextureProcessMode::LL:
shader.AddLine(
"float lod = " +
regs.GetRegisterAsInteger(instr.gpr20.Value() + extra_op_offset) + ';');
return "texelFetch(" + sampler + ", coords, lod)";
default:
UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
static_cast<u32>(instr.tlds.GetTextureProcessMode()));
return "texelFetch(" + sampler + ", coords, 0)";
}
}();
WriteTexsInstruction(instr, texture);
shader.AddLine(coord);
shader.AddLine("vec4 texture_tmp = " + texture + ';');
WriteTexsInstructionFloat(instr, "texture_tmp");
break;
}
case OpCode::Id::TLD4: {
ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
ASSERT(instr.tld4.array == 0);
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
@@ -2939,56 +2944,29 @@ private:
"NDV is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
"PTP is not implemented");
auto texture_type = instr.tld4.texture_type.Value();
const bool depth_compare =
instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
auto texture_type = instr.tld4.texture_type.Value();
u32 num_coordinates = TextureCoordinates(texture_type);
if (depth_compare)
num_coordinates += 1;
const bool is_array = instr.tld4.array != 0;
const auto [coord, texture] =
GetTLD4Code(instr, texture_type, depth_compare, is_array);
const auto scope = shader.Scope();
switch (num_coordinates) {
case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
static_cast<u32>(num_coordinates));
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
texture_type = Tegra::Shader::TextureType::Texture2D;
}
shader.AddLine(coord);
std::size_t dest_elem{};
const std::string sampler =
GetSampler(instr.sampler, texture_type, false, depth_compare);
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4.component) + ')';
if (depth_compare) {
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
} else {
std::size_t dest_elem{};
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
++dest_elem;
shader.AddLine("vec4 texture_tmp = " + texture + ';');
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false,
dest_elem);
++dest_elem;
}
break;
}
@@ -3002,27 +2980,31 @@ private:
const auto scope = shader.Scope();
std::string coords;
const bool depth_compare =
instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
const std::string sampler = GetSampler(
instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
if (depth_compare) {
// Note: TLD4S coordinate encoding works just like TEXS's
const std::string op_y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec3 coords = vec3(" + op_a + ", " + op_y + ", " + op_b + ");");
} else {
shader.AddLine("vec2 coords = vec2(" + op_a + ", " + op_b + ");");
}
std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4s.component) + ')';
if (depth_compare) {
texture = "vec4(" + texture + ')';
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
coords = "vec2 coords = vec2(" + op_a + ", ";
std::string texture = "textureGather(" + sampler + ", coords, ";
if (!depth_compare) {
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
coords += op_b + ");";
texture += std::to_string(instr.tld4s.component) + ')';
} else {
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
coords += op_b + ");";
texture += op_c + ')';
}
WriteTexsInstruction(instr, texture);
shader.AddLine(coords);
shader.AddLine("vec4 texture_tmp = " + texture + ';');
WriteTexsInstructionFloat(instr, "texture_tmp");
break;
}
case OpCode::Id::TXQ: {
@@ -3934,4 +3916,4 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u
return {};
}
} // namespace OpenGL::GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/assert.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
@@ -16,6 +17,8 @@ static constexpr u32 PROGRAM_OFFSET{10};
ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: VS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += R"(
@@ -84,6 +87,8 @@ void main() {
ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
// Version is intentionally skipped in shader generation, it's added by the lazy compilation.
std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: GS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_geometry();\n";
@@ -117,6 +122,8 @@ void main() {
ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: FS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_fragment();\n";

View File

@@ -177,6 +177,9 @@ struct ShaderSetup {
struct {
ProgramCode code;
ProgramCode code_b; // Used for dual vertex shaders
u64 unique_identifier;
std::size_t real_size;
std::size_t real_size_b;
} program;
/// Used in scenarios where we have a dual vertex shaders

View File

@@ -125,6 +125,75 @@ enum class SurfaceTarget {
TextureCubeArray,
};
constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
4, // ASTC_2D_8X8
4, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
4, // ASTC_2D_8X8_SRGB
4, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
4, // ASTC_2D_5X5
4, // ASTC_2D_5X5_SRGB
4, // ASTC_2D_10X8
4, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
/**
* Gets the compression factor for the specified PixelFormat. This applies to just the
* "compressed width" and "compressed height", not the overall compression factor of a
@@ -135,304 +204,237 @@ static constexpr u32 GetCompressionFactor(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
4, // ASTC_2D_8X8
4, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
4, // ASTC_2D_8X8_SRGB
4, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
4, // ASTC_2D_5X5
4, // ASTC_2D_5X5_SRGB
4, // ASTC_2D_10X8
4, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size());
return compression_factor_table[static_cast<std::size_t>(format)];
}
constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
8, // ASTC_2D_8X5
5, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
8, // ASTC_2D_8X5_SRGB
5, // ASTC_2D_5X4_SRGB
5, // ASTC_2D_5X5
5, // ASTC_2D_5X5_SRGB
10, // ASTC_2D_10X8
10, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
8, // ASTC_2D_8X5
5, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
8, // ASTC_2D_8X5_SRGB
5, // ASTC_2D_5X4_SRGB
5, // ASTC_2D_5X5
5, // ASTC_2D_5X5_SRGB
10, // ASTC_2D_10X8
10, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < block_width_table.size());
return block_width_table[static_cast<std::size_t>(format)];
}
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
5, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
5, // ASTC_2D_5X5
5, // ASTC_2D_5X5_SRGB
8, // ASTC_2D_10X8
8, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
5, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
5, // ASTC_2D_5X5
5, // ASTC_2D_5X5_SRGB
8, // ASTC_2D_10X8
8, // ASTC_2D_10X8_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
return block_height_table[static_cast<std::size_t>(format)];
}
constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
32, // ABGR8U
32, // ABGR8S
32, // ABGR8UI
16, // B5G6R5U
32, // A2B10G10R10U
16, // A1B5G5R5U
8, // R8U
8, // R8UI
64, // RGBA16F
64, // RGBA16U
64, // RGBA16UI
32, // R11FG11FB10F
128, // RGBA32UI
64, // DXT1
128, // DXT23
128, // DXT45
64, // DXN1
128, // DXN2UNORM
128, // DXN2SNORM
128, // BC7U
128, // BC6H_UF16
128, // BC6H_SF16
128, // ASTC_2D_4X4
16, // G8R8U
16, // G8R8S
32, // BGRA8
128, // RGBA32F
64, // RG32F
32, // R32F
16, // R16F
16, // R16U
16, // R16S
16, // R16UI
16, // R16I
32, // RG16
32, // RG16F
32, // RG16UI
32, // RG16I
32, // RG16S
96, // RGB32F
32, // RGBA8_SRGB
16, // RG8U
16, // RG8S
64, // RG32UI
32, // R32UI
128, // ASTC_2D_8X8
128, // ASTC_2D_8X5
128, // ASTC_2D_5X4
32, // BGRA8_SRGB
64, // DXT1_SRGB
128, // DXT23_SRGB
128, // DXT45_SRGB
128, // BC7U
128, // ASTC_2D_4X4_SRGB
128, // ASTC_2D_8X8_SRGB
128, // ASTC_2D_8X5_SRGB
128, // ASTC_2D_5X4_SRGB
128, // ASTC_2D_5X5
128, // ASTC_2D_5X5_SRGB
128, // ASTC_2D_10X8
128, // ASTC_2D_10X8_SRGB
32, // Z32F
16, // Z16
32, // Z24S8
32, // S8Z24
64, // Z32FS8
}};
static constexpr u32 GetFormatBpp(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
32, // ABGR8U
32, // ABGR8S
32, // ABGR8UI
16, // B5G6R5U
32, // A2B10G10R10U
16, // A1B5G5R5U
8, // R8U
8, // R8UI
64, // RGBA16F
64, // RGBA16U
64, // RGBA16UI
32, // R11FG11FB10F
128, // RGBA32UI
64, // DXT1
128, // DXT23
128, // DXT45
64, // DXN1
128, // DXN2UNORM
128, // DXN2SNORM
128, // BC7U
128, // BC6H_UF16
128, // BC6H_SF16
128, // ASTC_2D_4X4
16, // G8R8U
16, // G8R8S
32, // BGRA8
128, // RGBA32F
64, // RG32F
32, // R32F
16, // R16F
16, // R16U
16, // R16S
16, // R16UI
16, // R16I
32, // RG16
32, // RG16F
32, // RG16UI
32, // RG16I
32, // RG16S
96, // RGB32F
32, // RGBA8_SRGB
16, // RG8U
16, // RG8S
64, // RG32UI
32, // R32UI
128, // ASTC_2D_8X8
128, // ASTC_2D_8X5
128, // ASTC_2D_5X4
32, // BGRA8_SRGB
64, // DXT1_SRGB
128, // DXT23_SRGB
128, // DXT45_SRGB
128, // BC7U
128, // ASTC_2D_4X4_SRGB
128, // ASTC_2D_8X8_SRGB
128, // ASTC_2D_8X5_SRGB
128, // ASTC_2D_5X4_SRGB
128, // ASTC_2D_5X5
128, // ASTC_2D_5X5_SRGB
128, // ASTC_2D_10X8
128, // ASTC_2D_10X8_SRGB
32, // Z32F
16, // Z16
32, // Z24S8
32, // S8Z24
64, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < bpp_table.size());
return bpp_table[static_cast<std::size_t>(format)];
}

View File

@@ -35,6 +35,8 @@ add_executable(yuzu
configuration/configure_mouse_advanced.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_per_general.cpp
configuration/configure_per_general.h
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_web.cpp
@@ -86,6 +88,7 @@ set(UIS
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui

View File

@@ -206,60 +206,57 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
Settings::values.players[p].connected =
qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
auto& player = Settings::values.players[p];
Settings::values.players[p].type = static_cast<Settings::ControllerType>(
player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
player.type = static_cast<Settings::ControllerType>(
qt_config
->value(QString("player_%1_type").arg(p),
static_cast<u8>(Settings::ControllerType::DualJoycon))
.toUInt());
Settings::values.players[p].body_color_left =
qt_config
->value(QString("player_%1_body_color_left").arg(p),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
Settings::values.players[p].body_color_right =
qt_config
->value(QString("player_%1_body_color_right").arg(p),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
Settings::values.players[p].button_color_left =
qt_config
->value(QString("player_%1_button_color_left").arg(p),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
Settings::values.players[p].button_color_right =
qt_config
->value(QString("player_%1_button_color_right").arg(p),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
player.body_color_left = qt_config
->value(QString("player_%1_body_color_left").arg(p),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
player.body_color_right = qt_config
->value(QString("player_%1_body_color_right").arg(p),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
player.button_color_left = qt_config
->value(QString("player_%1_button_color_left").arg(p),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
player.button_color_right = qt_config
->value(QString("player_%1_button_color_right").arg(p),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.players[p].buttons[i] =
player.buttons[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.players[p].buttons[i].empty())
Settings::values.players[p].buttons[i] = default_param;
if (player.buttons[i].empty())
player.buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.players[p].analogs[i] =
player.analogs[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.players[p].analogs[i].empty())
Settings::values.players[p].analogs[i] = default_param;
if (player.analogs[i].empty())
player.analogs[i] = default_param;
}
}
@@ -444,6 +441,21 @@ void Config::ReadValues() {
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
qt_config->endGroup();
const auto size = qt_config->beginReadArray("DisabledAddOns");
for (int i = 0; i < size; ++i) {
qt_config->setArrayIndex(i);
const auto title_id = qt_config->value("title_id", 0).toULongLong();
std::vector<std::string> out;
const auto d_size = qt_config->beginReadArray("disabled");
for (int j = 0; j < d_size; ++j) {
qt_config->setArrayIndex(j);
out.push_back(qt_config->value("d", "").toString().toStdString());
}
qt_config->endArray();
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
qt_config->endArray();
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
@@ -511,30 +523,28 @@ void Config::ReadValues() {
}
void Config::SavePlayerValues() {
for (int p = 0; p < Settings::values.players.size(); ++p) {
qt_config->setValue(QString("player_%1_connected").arg(p),
Settings::values.players[p].connected);
qt_config->setValue(QString("player_%1_type").arg(p),
static_cast<u8>(Settings::values.players[p].type));
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
const auto& player = Settings::values.players[p];
qt_config->setValue(QString("player_%1_body_color_left").arg(p),
Settings::values.players[p].body_color_left);
qt_config->setValue(QString("player_%1_body_color_right").arg(p),
Settings::values.players[p].body_color_right);
qt_config->setValue(QString("player_%1_connected").arg(p), player.connected);
qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type));
qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left);
qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);
qt_config->setValue(QString("player_%1_button_color_left").arg(p),
Settings::values.players[p].button_color_left);
player.button_color_left);
qt_config->setValue(QString("player_%1_button_color_right").arg(p),
Settings::values.players[p].button_color_right);
player.button_color_right);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(Settings::values.players[p].buttons[i]));
QString::fromStdString(player.buttons[i]));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.players[p].analogs[i]));
QString::fromStdString(player.analogs[i]));
}
}
}
@@ -650,6 +660,21 @@ void Config::SaveValues() {
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
qt_config->endGroup();
qt_config->beginWriteArray("DisabledAddOns");
int i = 0;
for (const auto& elem : Settings::values.disabled_addons) {
qt_config->setArrayIndex(i);
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
qt_config->beginWriteArray("disabled");
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(j);
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
}
qt_config->endArray();
++i;
}
qt_config->endArray();
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);

View File

@@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
ui->output_sink_combo_box->clear();
ui->output_sink_combo_box->addItem("auto");
for (const auto& sink_detail : AudioCore::g_sink_details) {
ui->output_sink_combo_box->addItem(sink_detail.id);
for (const char* id : AudioCore::GetSinkIDs()) {
ui->output_sink_combo_box->addItem(id);
}
connect(ui->volume_slider, &QSlider::valueChanged, this,
@@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
for (const auto& device : device_list) {
for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
ui->audio_device_combo_box->addItem(QString::fromStdString(device));
}
}

View File

@@ -0,0 +1,170 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QHeaderView>
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QString>
#include <QTimer>
#include <QTreeView>
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "ui_configure_per_general.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_general.h"
#include "yuzu/ui_settings.h"
#include "yuzu/util/util.h"
ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties"));
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 2);
item_model->setHeaderData(0, Qt::Horizontal, "Patch Name");
item_model->setHeaderData(1, Qt::Horizontal, "Version");
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
scene = new QGraphicsScene;
ui->icon_view->setScene(scene);
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
this->loadConfiguration();
}
ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
void ConfigurePerGameGeneral::applyConfiguration() {
std::vector<std::string> disabled_addons;
for (const auto& item : list_items) {
const auto disabled = item.front()->checkState() == Qt::Unchecked;
if (disabled)
disabled_addons.push_back(item.front()->text().toStdString());
}
Settings::values.disabled_addons[title_id] = disabled_addons;
}
void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
this->file = std::move(file);
this->loadConfiguration();
}
void ConfigurePerGameGeneral::loadConfiguration() {
if (file == nullptr)
return;
const auto loader = Loader::GetLoader(file);
ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
FileSys::PatchManager pm{title_id};
const auto control = pm.GetControlMetadata();
if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
} else {
std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
ui->display_name->setText(QString::fromStdString(title));
std::string developer;
if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success)
ui->display_developer->setText(QString::fromStdString(developer));
ui->display_version->setText(QStringLiteral("1.0.0"));
}
if (control.second != nullptr) {
scene->clear();
QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), bytes.size());
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear();
QPixmap map;
map.loadFromData(bytes.data(), bytes.size());
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
QStandardItem* first_item = new QStandardItem;
const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
first_item->setText(name);
first_item->setCheckable(true);
const auto patch_disabled =
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
list_items.push_back(QList<QStandardItem*>{
first_item, new QStandardItem{QString::fromStdString(patch.second)}});
item_model->appendRow(list_items.back());
}
tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
ui->display_filename->setText(QString::fromStdString(file->GetName()));
ui->display_format->setText(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText);
}

View File

@@ -0,0 +1,50 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <QKeyEvent>
#include <QList>
#include <QWidget>
#include "core/file_sys/vfs_types.h"
class QTreeView;
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
namespace Ui {
class ConfigurePerGameGeneral;
}
class ConfigurePerGameGeneral : public QDialog {
Q_OBJECT
public:
explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
~ConfigurePerGameGeneral() override;
/// Save all button configurations to settings file
void applyConfiguration();
void loadFromFile(FileSys::VirtualFile file);
private:
std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
FileSys::VirtualFile file;
u64 title_id;
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
void loadConfiguration();
};

View File

@@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurePerGameGeneral</class>
<widget class="QDialog" name="ConfigurePerGameGeneral">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>520</height>
</rect>
</property>
<property name="windowTitle">
<string>ConfigurePerGameGeneral</string>
</property>
<layout class="QHBoxLayout" name="HorizontalLayout">
<item>
<layout class="QVBoxLayout" name="VerticalLayout">
<item>
<widget class="QGroupBox" name="GeneralGroupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="6" column="1" colspan="2">
<widget class="QLineEdit" name="display_filename">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="display_name">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Developer</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="display_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="display_version">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="display_format">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="display_developer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title ID</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="display_title_id">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="5">
<widget class="QGraphicsView" name="icon_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="PerformanceGroupBox">
<property name="title">
<string>Add-Ons</string>
</property>
<layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>169</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigurePerGameGeneral</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>269</x>
<y>567</y>
</hint>
<hint type="destinationlabel">
<x>269</x>
<y>294</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGameGeneral</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>269</x>
<y>567</y>
</hint>
<hint type="destinationlabel">
<x>269</x>
<y>294</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
open_save_location->setEnabled(program_id != 0);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered,
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}

View File

@@ -70,6 +70,7 @@ signals:
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
void OpenPerGameGeneralRequested(const std::string& file);
private slots:
void onTextChanged(const QString& newText);

View File

@@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
FileSys::VirtualFile update_raw;
loader.ReadUpdateRaw(update_raw);
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
const bool is_update = kv.first == "Update";
const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
if (!updatable && is_update) {
continue;
}
@@ -86,6 +86,37 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
out.chop(1);
return out;
}
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
const std::vector<u8>& icon, Loader::AppLoader& loader,
u64 program_id, const CompatibilityList& compatibility_list,
const FileSys::PatchManager& patch) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games
QString compatibility{"99"};
if (it != compatibility_list.end()) {
compatibility = it->second.first;
}
const auto file_type = loader.GetFileType();
const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
QList<QStandardItem*> list{
new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
new GameListItemSize(FileUtil::GetSize(path)),
};
if (UISettings::values.show_add_ons) {
list.insert(
2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
}
return list;
}
} // Anonymous namespace
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
@@ -116,29 +147,8 @@ void GameListWorker::AddInstalledTitlesToGameList() {
if (control != nullptr)
GetMetadataFromControlNCA(patch, *control, icon, name);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games
QString compatibility("99");
if (it != compatibility_list.end())
compatibility = it->second.first;
QList<QStandardItem*> list{
new GameListItemPath(
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItemCompat(compatibility),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(file->GetSize()),
};
if (UISettings::values.show_add_ons) {
list.insert(2, new GameListItem(FormatPatchNameVersions(patch, *loader)));
}
emit EntryReady(list);
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch));
}
const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
@@ -155,14 +165,14 @@ void GameListWorker::AddInstalledTitlesToGameList() {
void GameListWorker::FillControlMap(const std::string& dir_path) {
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing) {
// Breaks the callback loop
return false;
}
if (stop_processing)
return false; // Breaks the callback loop.
bool is_dir = FileUtil::IsDirectory(physical_name);
QFileInfo file_info(physical_name.c_str());
if (!is_dir && file_info.suffix().toStdString() == "nca") {
const std::string physical_name = directory + DIR_SEP + virtual_name;
const QFileInfo file_info(QString::fromStdString(physical_name));
if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
auto nca =
std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (nca->GetType() == FileSys::NCAContentType::Control) {
@@ -179,20 +189,25 @@ void GameListWorker::FillControlMap(const std::string& dir_path) {
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing) {
// Breaks the callback loop.
return false;
}
if (stop_processing)
return false; // Breaks the callback loop.
bool is_dir = FileUtil::IsDirectory(physical_name);
const std::string physical_name = directory + DIR_SEP + virtual_name;
const bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
std::unique_ptr<Loader::AppLoader> loader =
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
loader->GetFileType() == Loader::FileType::Error) &&
!UISettings::values.show_unknown))
auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (!loader) {
return true;
}
const auto file_type = loader->GetFileType();
if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) &&
!UISettings::values.show_unknown) {
return true;
}
std::vector<u8> icon;
const auto res1 = loader->ReadIcon(icon);
@@ -214,30 +229,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
}
}
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games
QString compatibility("99");
if (it != compatibility_list.end())
compatibility = it->second.first;
QList<QStandardItem*> list{
new GameListItemPath(
FormatGameName(physical_name), icon, QString::fromStdString(name),
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItemCompat(compatibility),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
};
if (UISettings::values.show_add_ons) {
list.insert(2, new GameListItem(FormatPatchNameVersions(
patch, *loader, loader->IsRomFSUpdatable())));
}
emit EntryReady(std::move(list));
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
compatibility_list, patch));
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
AddFstEntriesToGameList(physical_name, recursion - 1);

View File

@@ -9,6 +9,7 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/software_keyboard.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -441,6 +442,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
@@ -988,6 +991,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
}
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
u64 title_id{};
const auto v_file = Core::GetGameFileFromPath(vfs, file);
const auto loader = Loader::GetLoader(v_file);
if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"),
tr("The game properties could not be loaded."));
return;
}
ConfigurePerGameGeneral dialog(this, title_id);
dialog.loadFromFile(v_file);
auto result = dialog.exec();
if (result == QDialog::Accepted) {
dialog.applyConfiguration();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
game_list->PopulateAsync(UISettings::values.gamedir,
UISettings::values.gamedir_deepscan);
}
config->Save();
}
}
void GMainWindow::OnMenuLoadFile() {
const QString extensions =
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");

View File

@@ -168,6 +168,7 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnGameListOpenPerGameProperties(const std::string& file);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
#include <sstream>
#include <SDL.h>
#include <inih/cpp/INIReader.h>
#include "common/file_util.h"
@@ -369,6 +370,23 @@ void Config::ReadValues() {
Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
const auto title_id = std::stoul(line, nullptr, 16);
const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
std::string inner_line;
std::vector<std::string> out;
while (std::getline(inner_ss, inner_line, '|')) {
out.push_back(inner_line);
}
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
// Web Service
Settings::values.enable_telemetry =
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);

View File

@@ -221,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org
# See https://profile.yuzu-emu.org/ for more info
yuzu_username =
yuzu_token =
[AddOns]
# Used to disable add-ons
# List of title IDs of games that will have add-ons disabled (separated by '|'):
title_ids =
# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
)";
}