Compare commits

..

69 Commits

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

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

if (curr_thread == handle_thread) {
   result = total_thread_ticks + (hardware_tick_count - last_context_switch_ticks);
} else if (curr_thread == handle_thread && sub_id == current_core_index) {
   result = hardware_tick_count - last_context_switch_ticks;
}
2018-10-26 12:49:11 -04:00
bunnei
58444a0376 gl_rasterizer: Implement primitive restart. 2018-10-26 00:42:57 -04:00
bunnei
d278f25bda Merge pull request #1533 from FernandoS27/lmem
Implemented Shader Local Memory
2018-10-26 00:16:25 -04:00
bunnei
72e6b31a07 Merge pull request #1430 from DarkLordZach/remove-promote-dir
vfs: Remove InterpretAsDirectory and related functions
2018-10-26 00:15:34 -04:00
bunnei
1f98dc30ea Merge pull request #1591 from bunnei/depth-range
gl_rasterizer: Implement depth range.
2018-10-26 00:12:20 -04:00
bunnei
949d9a7136 maxwell_3d: Add code for initializing register defaults. 2018-10-25 23:42:39 -04:00
bunnei
debabf1fa6 Merge pull request #1569 from lioncash/amiibo
yuzu/main: Notify user of loading errors with Amiibo data
2018-10-25 22:10:08 -04:00
bunnei
476b9f8fc5 Merge pull request #1587 from lioncash/private
configure_system: Minor cleanup-related changes
2018-10-25 22:08:06 -04:00
bunnei
a94831f2a9 Merge pull request #1557 from bunnei/ldr_ro
Implement the LoadNro functions from the ldr:ro service.
2018-10-25 21:54:35 -04:00
bunnei
8cea598158 gl_rasterizer: Implement depth range. 2018-10-25 21:53:24 -04:00
bunnei
cebce2a93a ldr: Partially implement LoadNro.
- This is an incomplete implementation. It was tested with Super Mario Party.
2018-10-25 18:03:54 -04:00
bunnei
c2049aa4e5 process: LoadModule should clear JIT instruction cache. 2018-10-25 18:03:54 -04:00
bunnei
a609b6907a Kernel/Memory: Added a function to first a suitable guest address at which to allocate a region of a given size. 2018-10-25 18:03:54 -04:00
bunnei
ef7b2237d9 nro: Make LoadNro method accessible outside of apploader code. 2018-10-25 18:03:54 -04:00
Mat M
a141b46b5c Merge pull request #1583 from DarkLordZach/rle-size
ips_layer: Use rle_size instead of data_size in RLE patch application
2018-10-25 17:54:38 -04:00
Lioncash
85285b09b0 configure_system: Make the file selector text translatable
This should be localizable, since it's user-facing text.
2018-10-25 17:27:30 -04:00
Lioncash
5172354e29 configure_system: Make GetAccountUsername() an internal function
We can just make the function accept an arbitrary ProfileManager
reference and operate on that instead of tying the function to the class
itself. This allows us to keep the function internal to the cpp file and
removes the need to forward declare the UUID struct.
2018-10-25 17:27:25 -04:00
Lioncash
bf7da804c5 configure_system: Default initialize member variables
These should be initialized to deterministic values so it's easier to
catch improper behavior, as it'll always be reproducable, instead of
performing uninitialized reads.
2018-10-25 16:52:23 -04:00
Lioncash
8806e69f59 configure_system: Simplify UUID generation call in AddUser()
This is a static function so we can just perform an assignment directly.
2018-10-25 16:50:07 -04:00
Lioncash
a6addb5332 configure_system: Amend function casing 2018-10-25 16:47:09 -04:00
Lioncash
2347e1b8c5 configure_system: Add missing override specifier on the destructor 2018-10-25 16:45:13 -04:00
Lioncash
3c63cecb96 configure_system: Make public slots private
These are only used within this class, so we can make them private to
keep their use contained. This also gets rid of the pre-Qt5 'slot'
identifier, since Qt 5's connection syntax doesn't require a function to
be declared a slot anymore.
2018-10-25 16:43:44 -04:00
James Rowe
e54c9e19f3 Merge pull request #1584 from FearlessTobi/patch-3
Delete git file
2018-10-25 13:55:10 -06:00
Zach Hilman
9a87ece837 ips_layer: Use rle_size instead of data_size in RLE patch application
Prevents a potential bug when using RLE records in an IPS patch.
2018-10-25 14:23:56 -04:00
Frederic L
942def7831 Update MACOSX_DEPLOYMENT_TARGET to 10.14 2018-10-25 08:35:42 +02:00
FernandoS27
ca142f35c0 Implemented LD_L and ST_L 2018-10-24 17:51:53 -04:00
FernandoS27
abefe29398 Implement Shader Local Memory 2018-10-24 17:50:43 -04:00
Lioncash
bed2d6c425 yuzu/main: Notify user of loading errors with Amiibo data
We shouldn't silently continue if loading failed, since the general
assumption is that no messages showing up implicitly indicates success.
2018-10-24 10:39:31 -04:00
Zach Hilman
a25d79cfaa vfs: Remove InterpretAsDirectory and related functions
When writing VFS, it initally seemed useful to include a function to in-place convert container files into directories in one homogenous directory structure, but re-evaluating it now there have been plenty of chances to use it and there has always been a better way. Removing as it is unused and likely will not be used.
2018-10-19 14:02:07 -04:00
61 changed files with 1208 additions and 366 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -467,6 +467,37 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
enum class GetInfoType : u64 {
// 1.0.0+
AllowedCpuIdBitmask = 0,
AllowedThreadPrioBitmask = 1,
MapRegionBaseAddr = 2,
MapRegionSize = 3,
HeapRegionBaseAddr = 4,
HeapRegionSize = 5,
TotalMemoryUsage = 6,
TotalHeapUsage = 7,
IsCurrentProcessBeingDebugged = 8,
ResourceHandleLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
// 2.0.0+
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
NewMapRegionBaseAddr = 14,
NewMapRegionSize = 15,
// 3.0.0+
IsVirtualAddressMemoryEnabled = 16,
PersonalMmHeapUsage = 17,
TitleId = 18,
// 4.0.0+
PrivilegedProcessId = 19,
// 5.0.0+
UserExceptionContextAddr = 20,
ThreadTickCount = 0xF0000002,
};
const auto* current_process = Core::CurrentProcess();
const auto& vm_manager = current_process->VMManager();
@@ -529,6 +560,36 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
"(STUBBED) Attempted to query user exception context address, returned 0");
*result = 0;
break;
case GetInfoType::ThreadTickCount: {
constexpr u64 num_cpus = 4;
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
return ERR_INVALID_COMBINATION_KERNEL;
}
const auto thread =
current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
if (!thread) {
return ERR_INVALID_HANDLE;
}
auto& system = Core::System::GetInstance();
const auto& scheduler = system.CurrentScheduler();
const auto* const current_thread = scheduler.GetCurrentThread();
const bool same_thread = current_thread == thread;
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
u64 out_ticks = 0;
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
}
*result = out_ticks;
break;
}
default:
UNIMPLEMENTED();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,6 +40,10 @@ static bool IsPixelFormatASTC(PixelFormat format) {
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
return true;
default:
return false;
@@ -56,6 +60,14 @@ static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
return {8, 8};
case PixelFormat::ASTC_2D_8X5:
return {8, 5};
case PixelFormat::ASTC_2D_4X4_SRGB:
return {4, 4};
case PixelFormat::ASTC_2D_5X4_SRGB:
return {5, 4};
case PixelFormat::ASTC_2D_8X8_SRGB:
return {8, 8};
case PixelFormat::ASTC_2D_8X5_SRGB:
return {8, 5};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
@@ -108,8 +120,9 @@ std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const {
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
params.pixel_format =
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value());
params.srgb_conversion = config.tic.IsSrgbConversionEnabled();
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
params.type = GetFormatType(params.pixel_format);
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
@@ -166,6 +179,8 @@ std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const {
params.block_height = 1 << config.memory_layout.block_height;
params.block_depth = 1 << config.memory_layout.block_depth;
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
@@ -201,6 +216,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const {
params.pixel_format = PixelFormatFromDepthFormat(format);
params.component_type = ComponentTypeFromDepthFormat(format);
params.type = GetFormatType(params.pixel_format);
params.srgb_conversion = false;
params.width = zeta_width;
params.height = zeta_height;
params.unaligned_height = zeta_height;
@@ -224,6 +240,8 @@ std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const {
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
@@ -255,7 +273,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI
{GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT1
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT23
@@ -289,14 +307,29 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I
{GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S
{GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // SRGBA8
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm,
false}, // RGBA8_SRGB
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4
{GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8
// Compressed sRGB formats
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT1_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT23_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT45_SRGB
{GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8,
ComponentType::UNorm, true}, // BC7U_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB
// Depth formats
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
@@ -361,6 +394,10 @@ static bool IsFormatBCn(PixelFormat format) {
case PixelFormat::BC7U:
case PixelFormat::BC6H_UF16:
case PixelFormat::BC6H_SF16:
case PixelFormat::DXT1_SRGB:
case PixelFormat::DXT23_SRGB:
case PixelFormat::DXT45_SRGB:
case PixelFormat::BC7U_SRGB:
return true;
}
return false;
@@ -432,7 +469,7 @@ static constexpr GLConversionArray morton_to_gl_fns = {
MortonCopy<true, PixelFormat::RG16I>,
MortonCopy<true, PixelFormat::RG16S>,
MortonCopy<true, PixelFormat::RGB32F>,
MortonCopy<true, PixelFormat::SRGBA8>,
MortonCopy<true, PixelFormat::RGBA8_SRGB>,
MortonCopy<true, PixelFormat::RG8U>,
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::RG32UI>,
@@ -440,6 +477,15 @@ static constexpr GLConversionArray morton_to_gl_fns = {
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
MortonCopy<true, PixelFormat::BGRA8_SRGB>,
MortonCopy<true, PixelFormat::DXT1_SRGB>,
MortonCopy<true, PixelFormat::DXT23_SRGB>,
MortonCopy<true, PixelFormat::DXT45_SRGB>,
MortonCopy<true, PixelFormat::BC7U_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z24S8>,
@@ -491,7 +537,7 @@ static constexpr GLConversionArray gl_to_morton_fns = {
MortonCopy<false, PixelFormat::RG16I>,
MortonCopy<false, PixelFormat::RG16S>,
MortonCopy<false, PixelFormat::RGB32F>,
MortonCopy<false, PixelFormat::SRGBA8>,
MortonCopy<false, PixelFormat::RGBA8_SRGB>,
MortonCopy<false, PixelFormat::RG8U>,
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::RG32UI>,
@@ -499,6 +545,15 @@ static constexpr GLConversionArray gl_to_morton_fns = {
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::BGRA8_SRGB>,
MortonCopy<false, PixelFormat::DXT1_SRGB>,
MortonCopy<false, PixelFormat::DXT23_SRGB>,
MortonCopy<false, PixelFormat::DXT45_SRGB>,
MortonCopy<false, PixelFormat::BC7U_SRGB>,
nullptr,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z24S8>,
@@ -546,6 +601,8 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
OpenGLState state;
state.draw.read_framebuffer = read_fb_handle;
state.draw.draw_framebuffer = draw_fb_handle;
// Set sRGB enabled if the destination surfaces need it
state.framebuffer_srgb.enabled = dst_params.srgb_conversion;
state.Apply();
u32 buffers{};
@@ -881,7 +938,11 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
case PixelFormat::ASTC_2D_5X4: {
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB: {
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
u32 block_width{};
u32 block_height{};
@@ -913,7 +974,9 @@ static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelForm
case PixelFormat::G8R8U:
case PixelFormat::G8R8S:
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8: {
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB: {
LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
static_cast<u32>(pixel_format));
UNREACHABLE();
@@ -960,8 +1023,8 @@ void CachedSurface::FlushGLBuffer() {
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
ASSERT(!tuple.compressed);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(),
gl_buffer.data());
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type,
static_cast<GLsizei>(gl_buffer.size()), gl_buffer.data());
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
params.height);

View File

@@ -69,7 +69,7 @@ struct SurfaceParams {
RG16I = 37,
RG16S = 38,
RGB32F = 39,
SRGBA8 = 40,
RGBA8_SRGB = 40,
RG8U = 41,
RG8S = 42,
RG32UI = 43,
@@ -77,19 +77,28 @@ struct SurfaceParams {
ASTC_2D_8X8 = 45,
ASTC_2D_8X5 = 46,
ASTC_2D_5X4 = 47,
BGRA8_SRGB = 48,
DXT1_SRGB = 49,
DXT23_SRGB = 50,
DXT45_SRGB = 51,
BC7U_SRGB = 52,
ASTC_2D_4X4_SRGB = 53,
ASTC_2D_8X8_SRGB = 54,
ASTC_2D_8X5_SRGB = 55,
ASTC_2D_5X4_SRGB = 56,
MaxColorFormat,
// Depth formats
Z32F = 48,
Z16 = 49,
Z32F = 57,
Z16 = 58,
MaxDepthFormat,
// DepthStencil formats
Z24S8 = 50,
S8Z24 = 51,
Z32FS8 = 52,
Z24S8 = 59,
S8Z24 = 60,
Z32FS8 = 61,
MaxDepthStencilFormat,
@@ -236,7 +245,7 @@ struct SurfaceParams {
1, // RG16I
1, // RG16S
1, // RGB32F
1, // SRGBA8
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
@@ -244,6 +253,15 @@ struct SurfaceParams {
4, // ASTC_2D_8X8
4, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
4, // ASTC_2D_8X8_SRGB
4, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
@@ -255,6 +273,77 @@ struct SurfaceParams {
return compression_factor_table[static_cast<std::size_t>(format)];
}
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // ABGR8U
1, // ABGR8S
1, // ABGR8UI
1, // B5G6R5U
1, // A2B10G10R10U
1, // A1B5G5R5U
1, // R8U
1, // R8UI
1, // RGBA16F
1, // RGBA16U
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
4, // DXT23
4, // DXT45
4, // DXN1
4, // DXN2UNORM
4, // DXN2SNORM
4, // BC7U
4, // BC6H_UF16
4, // BC6H_SF16
4, // ASTC_2D_4X4
1, // G8R8U
1, // G8R8S
1, // BGRA8
1, // RGBA32F
1, // RG32F
1, // R32F
1, // R16F
1, // R16U
1, // R16S
1, // R16UI
1, // R16I
1, // RG16
1, // RG16F
1, // RG16UI
1, // RG16I
1, // RG16S
1, // RGB32F
1, // RGBA8_SRGB
1, // RG8U
1, // RG8S
1, // RG32UI
1, // R32UI
8, // ASTC_2D_8X8
5, // ASTC_2D_8X5
4, // ASTC_2D_5X4
1, // BGRA8_SRGB
4, // DXT1_SRGB
4, // DXT23_SRGB
4, // DXT45_SRGB
4, // BC7U_SRGB
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
1, // Z32F
1, // Z16
1, // Z24S8
1, // S8Z24
1, // Z32FS8
}};
ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
return block_height_table[static_cast<std::size_t>(format)];
}
static constexpr u32 GetFormatBpp(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
@@ -300,14 +389,23 @@ struct SurfaceParams {
32, // RG16I
32, // RG16S
96, // RGB32F
32, // SRGBA8
32, // RGBA8_SRGB
16, // RG8U
16, // RG8S
64, // RG32UI
32, // R32UI
16, // ASTC_2D_8X8
32, // ASTC_2D_8X5
16, // ASTC_2D_8X5
32, // ASTC_2D_5X4
32, // BGRA8_SRGB
64, // DXT1_SRGB
128, // DXT23_SRGB
128, // DXT45_SRGB
128, // BC7U
32, // ASTC_2D_4X4_SRGB
16, // ASTC_2D_8X8_SRGB
16, // ASTC_2D_8X5_SRGB
32, // ASTC_2D_5X4_SRGB
32, // Z32F
16, // Z16
32, // Z24S8
@@ -346,6 +444,7 @@ struct SurfaceParams {
// TODO (Hexagon12): Converting SRGBA to RGBA is a hack and doesn't completely correct the
// gamma.
case Tegra::RenderTargetFormat::RGBA8_SRGB:
return PixelFormat::RGBA8_SRGB;
case Tegra::RenderTargetFormat::RGBA8_UNORM:
return PixelFormat::ABGR8U;
case Tegra::RenderTargetFormat::RGBA8_SNORM:
@@ -353,6 +452,7 @@ struct SurfaceParams {
case Tegra::RenderTargetFormat::RGBA8_UINT:
return PixelFormat::ABGR8UI;
case Tegra::RenderTargetFormat::BGRA8_SRGB:
return PixelFormat::BGRA8_SRGB;
case Tegra::RenderTargetFormat::BGRA8_UNORM:
return PixelFormat::BGRA8;
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
@@ -416,10 +516,14 @@ struct SurfaceParams {
}
static PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
Tegra::Texture::ComponentType component_type) {
Tegra::Texture::ComponentType component_type,
bool is_srgb) {
// TODO(Subv): Properly implement this
switch (format) {
case Tegra::Texture::TextureFormat::A8R8G8B8:
if (is_srgb) {
return PixelFormat::RGBA8_SRGB;
}
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::ABGR8U;
@@ -554,11 +658,11 @@ struct SurfaceParams {
case Tegra::Texture::TextureFormat::Z24S8:
return PixelFormat::Z24S8;
case Tegra::Texture::TextureFormat::DXT1:
return PixelFormat::DXT1;
return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
case Tegra::Texture::TextureFormat::DXT23:
return PixelFormat::DXT23;
return is_srgb ? PixelFormat::DXT23_SRGB : PixelFormat::DXT23;
case Tegra::Texture::TextureFormat::DXT45:
return PixelFormat::DXT45;
return is_srgb ? PixelFormat::DXT45_SRGB : PixelFormat::DXT45;
case Tegra::Texture::TextureFormat::DXN1:
return PixelFormat::DXN1;
case Tegra::Texture::TextureFormat::DXN2:
@@ -572,19 +676,19 @@ struct SurfaceParams {
static_cast<u32>(component_type));
UNREACHABLE();
case Tegra::Texture::TextureFormat::BC7U:
return PixelFormat::BC7U;
return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U;
case Tegra::Texture::TextureFormat::BC6H_UF16:
return PixelFormat::BC6H_UF16;
case Tegra::Texture::TextureFormat::BC6H_SF16:
return PixelFormat::BC6H_SF16;
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
return PixelFormat::ASTC_2D_4X4;
return is_srgb ? PixelFormat::ASTC_2D_4X4_SRGB : PixelFormat::ASTC_2D_4X4;
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
return PixelFormat::ASTC_2D_5X4;
return is_srgb ? PixelFormat::ASTC_2D_5X4_SRGB : PixelFormat::ASTC_2D_5X4;
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
return PixelFormat::ASTC_2D_8X8;
return is_srgb ? PixelFormat::ASTC_2D_8X8_SRGB : PixelFormat::ASTC_2D_8X8;
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
return PixelFormat::ASTC_2D_8X5;
return is_srgb ? PixelFormat::ASTC_2D_8X5_SRGB : PixelFormat::ASTC_2D_8X5;
case Tegra::Texture::TextureFormat::R16_G16:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
@@ -819,7 +923,7 @@ struct SurfaceParams {
SurfaceTarget target;
u32 max_mip_level;
bool is_layered;
bool srgb_conversion;
// Parameters used for caching
VAddr addr;
Tegra::GPUVAddr gpu_addr;

View File

@@ -278,7 +278,7 @@ public:
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
const Tegra::Shader::Header& header)
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header},
fixed_pipeline_output_attributes_used{} {
fixed_pipeline_output_attributes_used{}, local_memory_size{0} {
BuildRegisterList();
BuildInputList();
}
@@ -341,10 +341,10 @@ public:
*/
void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value,
u64 dest_num_components, u64 value_num_components,
bool is_saturated = false, u64 dest_elem = 0) {
bool is_saturated = false, u64 dest_elem = 0, bool precise = false) {
SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value,
dest_num_components, value_num_components, dest_elem);
dest_num_components, value_num_components, dest_elem, precise);
}
/**
@@ -368,7 +368,7 @@ public:
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')',
dest_num_components, value_num_components, dest_elem);
dest_num_components, value_num_components, dest_elem, false);
if (sets_cc) {
const std::string zero_condition = "( " + ConvertIntegerSize(value, size) + " == 0 )";
@@ -416,7 +416,7 @@ public:
}
}();
SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem);
SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem, false);
}
/**
@@ -436,6 +436,25 @@ public:
shader.AddLine(dest + " = " + src + ';');
}
std::string GetLocalMemoryAsFloat(const std::string& index) {
return "lmem[" + index + ']';
}
std::string GetLocalMemoryAsInteger(const std::string& index, bool is_signed = false) {
const std::string func{is_signed ? "floatToIntBits" : "floatBitsToUint"};
return func + "(lmem[" + index + "])";
}
void SetLocalMemoryAsFloat(const std::string& index, const std::string& value) {
shader.AddLine("lmem[" + index + "] = " + value + ';');
}
void SetLocalMemoryAsInteger(const std::string& index, const std::string& value,
bool is_signed = false) {
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
shader.AddLine("lmem[" + index + "] = " + func + '(' + value + ");");
}
std::string GetControlCode(const Tegra::Shader::ControlCode cc) const {
switch (cc) {
case Tegra::Shader::ControlCode::NEU:
@@ -533,6 +552,7 @@ public:
void GenerateDeclarations(const std::string& suffix) {
GenerateVertex();
GenerateRegisters(suffix);
GenerateLocalMemory();
GenerateInternalFlags();
GenerateInputAttrs();
GenerateOutputAttrs();
@@ -578,6 +598,10 @@ public:
return entry.GetName();
}
void SetLocalMemory(u64 lmem) {
local_memory_size = lmem;
}
private:
/// Generates declarations for registers.
void GenerateRegisters(const std::string& suffix) {
@@ -588,6 +612,15 @@ private:
declarations.AddNewLine();
}
/// Generates declarations for local memory.
void GenerateLocalMemory() {
if (local_memory_size > 0) {
declarations.AddLine("float lmem[" + std::to_string((local_memory_size - 1 + 4) / 4) +
"];");
declarations.AddNewLine();
}
}
/// Generates declarations for internal flags.
void GenerateInternalFlags() {
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
@@ -724,7 +757,8 @@ private:
* @param dest_elem Optional, the destination element to use for the operation.
*/
void SetRegister(const Register& reg, u64 elem, const std::string& value,
u64 dest_num_components, u64 value_num_components, u64 dest_elem) {
u64 dest_num_components, u64 value_num_components, u64 dest_elem,
bool precise) {
if (reg == Register::ZeroIndex) {
LOG_CRITICAL(HW_GPU, "Cannot set Register::ZeroIndex");
UNREACHABLE();
@@ -741,7 +775,18 @@ private:
src += GetSwizzle(elem);
}
shader.AddLine(dest + " = " + src + ';');
if (precise && stage != Maxwell3D::Regs::ShaderStage::Fragment) {
shader.AddLine('{');
++shader.scope;
// This avoids optimizations of constant propagation and keeps the code as the original
// Sadly using the precise keyword causes "linking" errors on fragment shaders.
shader.AddLine("precise float tmp = " + src + ';');
shader.AddLine(dest + " = tmp;");
--shader.scope;
shader.AddLine('}');
} else {
shader.AddLine(dest + " = " + src + ';');
}
}
/// Build the GLSL register list.
@@ -895,6 +940,7 @@ private:
const std::string& suffix;
const Tegra::Shader::Header& header;
std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used;
u64 local_memory_size;
};
class GLSLGenerator {
@@ -904,6 +950,8 @@ public:
: subroutines(subroutines), program_code(program_code), main_offset(main_offset),
stage(stage), suffix(suffix) {
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
local_memory_size = header.GetLocalMemorySize();
regs.SetLocalMemory(local_memory_size);
Generate(suffix);
}
@@ -1474,8 +1522,9 @@ private:
ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented");
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Id::FADD_C:
@@ -1483,8 +1532,9 @@ private:
case OpCode::Id::FADD_IMM: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Id::MUFU: {
@@ -1492,31 +1542,31 @@ private:
switch (instr.sub_op) {
case SubOp::Cos:
regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Sin:
regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Ex2:
regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Lg2:
regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Rcp:
regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Rsq:
regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
case SubOp::Sqrt:
regs.SetRegisterToFloat(instr.gpr0, 0, "sqrt(" + op_a + ')', 1, 1,
instr.alu.saturate_d);
instr.alu.saturate_d, 0, true);
break;
default:
LOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}",
@@ -1537,7 +1587,7 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0,
'(' + condition + ") ? min(" + parameters + ") : max(" +
parameters + ')',
1, 1);
1, 1, false, 0, true);
break;
}
case OpCode::Id::RRO_C:
@@ -1566,7 +1616,7 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0,
regs.GetRegisterAsFloat(instr.gpr8) + " * " +
GetImmediate32(instr),
1, 1, instr.fmul32.saturate);
1, 1, instr.fmul32.saturate, 0, true);
break;
}
case OpCode::Id::FADD32I: {
@@ -1589,7 +1639,7 @@ private:
op_b = "-(" + op_b + ')';
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1);
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, false, 0, true);
break;
}
}
@@ -2051,8 +2101,9 @@ private:
}
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1,
instr.alu.saturate_d);
regs.SetRegisterToFloat(instr.gpr0, 0, "fma(" + op_a + ", " + op_b + ", " + op_c + ')',
1, 1, instr.alu.saturate_d, 0, true);
break;
}
case OpCode::Type::Hfma2: {
@@ -2324,6 +2375,39 @@ private:
shader.AddLine("}");
break;
}
case OpCode::Id::LD_L: {
// Add an extra scope and declare the index register inside to prevent
// overwriting it in case it is used as an output of the LD instruction.
shader.AddLine('{');
++shader.scope;
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
std::to_string(instr.smem_imm.Value()) + ')';
shader.AddLine("uint index = (" + op + " / 4);");
const std::string op_a = regs.GetLocalMemoryAsFloat("index");
if (instr.ld_l.unknown != 1) {
LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}",
static_cast<unsigned>(instr.ld_l.unknown.Value()));
UNREACHABLE();
}
switch (instr.ldst_sl.type.Value()) {
case Tegra::Shader::StoreType::Bytes32:
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
break;
default:
LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}",
static_cast<unsigned>(instr.ldst_sl.type.Value()));
UNREACHABLE();
}
--shader.scope;
shader.AddLine('}');
break;
}
case OpCode::Id::ST_A: {
ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
"Indirect attribute loads are not supported");
@@ -2352,6 +2436,37 @@ private:
break;
}
case OpCode::Id::ST_L: {
// Add an extra scope and declare the index register inside to prevent
// overwriting it in case it is used as an output of the LD instruction.
shader.AddLine('{');
++shader.scope;
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
std::to_string(instr.smem_imm.Value()) + ')';
shader.AddLine("uint index = (" + op + " / 4);");
if (instr.st_l.unknown != 0) {
LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}",
static_cast<unsigned>(instr.st_l.unknown.Value()));
UNREACHABLE();
}
switch (instr.ldst_sl.type.Value()) {
case Tegra::Shader::StoreType::Bytes32:
regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0));
break;
default:
LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}",
static_cast<unsigned>(instr.ldst_sl.type.Value()));
UNREACHABLE();
}
--shader.scope;
shader.AddLine('}');
break;
}
case OpCode::Id::TEX: {
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
std::string coord;
@@ -3575,6 +3690,7 @@ private:
const u32 main_offset;
Maxwell3D::Regs::ShaderStage stage;
const std::string& suffix;
u64 local_memory_size;
ShaderWriter shader;
ShaderWriter declarations;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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