Compare commits

..

49 Commits

Author SHA1 Message Date
N00byKing
3357e8d9ba log.h: Change comment from citra to yuzu 2018-03-26 21:52:59 +02:00
N00byKing
75da830c13 file_util.h: Update Comment from citra to yuzu 2018-03-26 21:51:04 +02:00
N00byKing
99d86deb1f cpu_detect.cpp: Change comment from citra to yuzu 2018-03-26 21:43:39 +02:00
N00byKing
b326369704 pre-commit: Change comment from citra to yuzu 2018-03-26 21:34:19 +02:00
bunnei
8c8da93693 Merge pull request #280 from bunnei/misc-service-fixes
Minor changes to VI, PL, HID, and AUDREN
2018-03-25 17:07:06 -04:00
bunnei
e9315ace9f Merge pull request #273 from Subv/textures
GPU: Added code to unswizzle textures and ported the surface viewer from citra
2018-03-25 16:28:24 -04:00
bunnei
a0933d92fc Merge pull request #281 from mailwl/sockets-services
Service/sockets: add bsd:s, nsd:a, nsd:u services
2018-03-25 15:45:18 -04:00
mailwl
692639e9b7 Service/sockets: add bsd:s, nsd:a, nsd:u services 2018-03-25 12:41:00 +03:00
bunnei
6f27edccb2 audren_u: Fix GetAudioDevice. 2018-03-25 03:24:20 -04:00
bunnei
bde3e667be hid: Stub out SetNpadJoyAssignmentModeDual. 2018-03-25 03:24:05 -04:00
bunnei
868f7f18b9 pl_u: Add RequestLoad. 2018-03-25 03:23:52 -04:00
Subv
0ce52b1da2 GPU: Make the debug_context variable a member of the frontend instead of a global. 2018-03-24 23:35:06 -05:00
Subv
2c785bd06c GPU: Added a function to retrieve the active textures for a shader stage.
TODO: A shader may not use all of these textures at the same time, shader analysis should be performed to determine which textures are actually sampled.
2018-03-24 11:31:53 -05:00
Subv
39e60cfeb1 Frontend: Updated the surface view debug widget to work with Maxwell surfaces. 2018-03-24 11:31:53 -05:00
Subv
025d111308 Frontend: Allow opening the Surface View widget in the Qt frontend. 2018-03-24 11:31:50 -05:00
Subv
1c31e2b3d2 GPU: Implement the Incoming/FinishedPrimitiveBatch debug breakpoints. 2018-03-24 11:31:50 -05:00
Subv
1ad97c75a0 GPU: Implement the MaxwellCommandLoaded/Processed debug breakpoints. 2018-03-24 11:31:50 -05:00
Subv
77fd0d47e7 Frontend: Ported the GPU breakpoints and surface viewer widgets from citra. 2018-03-24 11:31:49 -05:00
Subv
1b8d798835 GPU: Added a method to unswizzle a texture without decoding it.
Allow unswizzling of DXT1 textures.
2018-03-24 11:30:56 -05:00
Subv
71ebc3e90d GPU: Preliminary work for texture decoding. 2018-03-24 11:30:56 -05:00
bunnei
46945b5c96 Merge pull request #275 from MerryMage/addticks-dynarmic
arm_dynarmic: Fix timing
2018-03-24 05:47:42 -04:00
MerryMage
88c9608eac arm_dynarmic: Fix timing 2018-03-24 09:02:19 +00:00
bunnei
31816aac38 Merge pull request #274 from Subv/viewport_regs
GPU: Added viewport registers to Maxwell3D's reg structure.
2018-03-24 02:42:16 -04:00
Subv
9b9de30086 GPU: Added viewport registers to Maxwell3D's reg structure. 2018-03-24 01:22:19 -05:00
bunnei
a10baacf9e Merge pull request #265 from bunnei/tegra-progress-2
Tegra progress 2
2018-03-23 23:30:48 -04:00
bunnei
a397a9e9a4 Merge pull request #255 from Subv/sd_card
FS: Implemented access to the SD card
2018-03-23 20:48:26 -04:00
bunnei
68658ce4b0 Merge pull request #268 from mailwl/ssl
Service/SSL: add ssl service
2018-03-23 15:30:17 -04:00
bunnei
fd0a7c0aaf Merge pull request #270 from N00byKing/patch-2
Remove Option for N/3DS from default.ini
2018-03-23 15:29:35 -04:00
bunnei
7ce4a03188 Merge pull request #269 from N00byKing/icontoyuzu
CITRA_ICON -> YUZU_ICON
2018-03-23 15:29:14 -04:00
Subv
4c06d55a81 FS: Move the file open mode calculation to a separate function. 2018-03-23 14:27:07 -05:00
N00byKing
32fd57f0c8 Remove Option for N/3DS from default.ini 2018-03-23 18:06:55 +01:00
N00byKing
5ded39f5d8 CITRA_ICON -> YUZU_ICON 2018-03-23 16:56:59 +01:00
mailwl
9ee33350de Service/SSL: add ssl service 2018-03-23 09:32:50 +03:00
bunnei
633abd5a94 Merge pull request #264 from valentinvanelslande/cmd-dynarmic
yuzu_cmd: change default cpu core to dynarmic
2018-03-22 23:53:15 -04:00
Valentin Vanelslande
ee5f5a2c2d yuzu_cmd: change default cpu core to dynarmic 2018-03-22 19:44:34 -06:00
Valentin Vanelslande
47cac816f6 default_ini: change default cpu core to dynarmic 2018-03-22 19:43:59 -06:00
bunnei
4daf91fc69 Merge pull request #263 from N00byKing/non3ds
Remove more N3DS References
2018-03-22 18:02:51 -04:00
N00byKing
8afdbf6a1f Remove more N3DS References 2018-03-22 21:25:06 +01:00
bunnei
b5c03088bc Merge pull request #261 from mailwl/spl
Service/spl: add module and services
2018-03-22 10:28:28 -04:00
mailwl
95e747cd06 Service/spl: add module and services 2018-03-22 09:55:14 +03:00
Subv
eff3f60b73 FS: Implemented IFileSystem::CreateDirectory. 2018-03-21 09:55:59 -05:00
Subv
0485ee499f FS: Implemented IFileSystem's OpenDirectory function.
Note that the filter parameter is not yet implemented.
2018-03-19 23:02:30 -05:00
Subv
21bac2d7d7 FS: Added the IDirectory IPC interface and implemented its two functions. 2018-03-19 23:01:47 -05:00
Subv
6d90d99d12 FS: Implement DiskFileSystem's OpenDirectory interface. 2018-03-19 23:00:37 -05:00
Subv
e1d7b9fc2c FS: Implement DiskFileSystem::GetEntryType for existing files/directories. 2018-03-19 22:59:38 -05:00
Subv
a9ba2c2000 FS: Updated the Directory Entry structure to match the Switch. 2018-03-19 22:58:55 -05:00
Subv
fc44261dd1 FS: Support the file Append open mode. 2018-03-19 22:57:34 -05:00
Subv
808704c78c FS: Implement MountSdCard. 2018-03-19 21:21:49 -05:00
Subv
c4ca802b9d FS: Added an SDMC archive factory and registered it to the SDMC archive on startup. 2018-03-19 21:17:15 -05:00
65 changed files with 2204 additions and 137 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/sh
# Enforce citra's whitespace policy
# Enforce yuzu's whitespace policy
git config --local core.whitespace tab-in-indent,trailing-space
paths_to_check="src/ CMakeLists.txt"

View File

@@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path);
// Set the current directory to given directory
bool SetCurrentDir(const std::string& directory);
// Returns a pointer to a string with a Citra data dir in the user's home
// Returns a pointer to a string with a yuzu data dir in the user's home
// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");

View File

@@ -48,6 +48,8 @@ namespace Log {
SUB(Service, PCTL) \
SUB(Service, SET) \
SUB(Service, SM) \
SUB(Service, SPL) \
SUB(Service, SSL) \
SUB(Service, Time) \
SUB(Service, VI) \
CLS(HW) \

View File

@@ -65,6 +65,8 @@ enum class Class : ClassType {
Service_PCTL, ///< The PCTL (Parental control) service
Service_SET, ///< The SET (Settings) service
Service_SM, ///< The SM (Service manager) service
Service_SPL, ///< The SPL service
Service_SSL, ///< The SSL service
Service_Time, ///< The time service
Service_VI, ///< The VI (Video interface) service
HW, ///< Low-level hardware emulation
@@ -83,7 +85,7 @@ enum class Class : ClassType {
Loader, ///< ROM loader
Input, ///< Input emulation
Network, ///< Network emulation
WebService, ///< Interface to Citra Web Services
WebService, ///< Interface to yuzu Web Services
Count ///< Total number of logging classes
};

View File

@@ -54,7 +54,7 @@ static CPUCaps Detect() {
caps.num_cores = std::thread::hardware_concurrency();
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
// Citra at all anyway
// yuzu at all anyway
int cpu_id[4];
memset(caps.brand_string, 0, sizeof(caps.brand_string));

View File

@@ -22,6 +22,8 @@ add_library(core STATIC
file_sys/romfs_filesystem.h
file_sys/savedata_factory.cpp
file_sys/savedata_factory.h
file_sys/sdmc_factory.cpp
file_sys/sdmc_factory.h
file_sys/storage.h
frontend/emu_window.cpp
frontend/emu_window.h
@@ -187,12 +189,22 @@ add_library(core STATIC
hle/service/sm/controller.h
hle/service/sm/sm.cpp
hle/service/sm/sm.h
hle/service/sockets/bsd_u.cpp
hle/service/sockets/bsd_u.h
hle/service/sockets/bsd.cpp
hle/service/sockets/bsd.h
hle/service/sockets/nsd.cpp
hle/service/sockets/nsd.h
hle/service/sockets/sfdnsres.cpp
hle/service/sockets/sfdnsres.h
hle/service/sockets/sockets.cpp
hle/service/sockets/sockets.h
hle/service/spl/csrng.cpp
hle/service/spl/csrng.h
hle/service/spl/module.cpp
hle/service/spl/module.h
hle/service/spl/spl.cpp
hle/service/spl/spl.h
hle/service/ssl/ssl.cpp
hle/service/ssl/ssl.h
hle/service/time/time.cpp
hle/service/time/time.h
hle/service/time/time_s.cpp

View File

@@ -86,21 +86,17 @@ public:
}
void AddTicks(u64 ticks) override {
if (ticks > ticks_remaining) {
ticks_remaining = 0;
return;
}
ticks -= ticks_remaining;
CoreTiming::AddTicks(ticks - num_interpreted_instructions);
num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
return ticks_remaining;
return std::max(CoreTiming::GetDowncount(), 0);
}
u64 GetCNTPCT() override {
return CoreTiming::GetTicks();
}
ARM_Dynarmic& parent;
size_t ticks_remaining = 0;
size_t num_interpreted_instructions = 0;
u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0;

View File

@@ -13,6 +13,7 @@
#include "core/memory.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/gpu.h"
class EmuWindow;
@@ -135,6 +136,14 @@ public:
return *app_loader;
}
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
debug_context = std::move(context);
}
std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const {
return debug_context;
}
private:
/**
* Initialize the emulated system.
@@ -154,6 +163,8 @@ private:
std::unique_ptr<Kernel::Scheduler> scheduler;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
Kernel::SharedPtr<Kernel::Process> current_process;
/// When true, signals that a reschedule should happen

View File

@@ -6,34 +6,28 @@
#include <array>
#include <cstddef>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/file_sys/filesystem.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// FileSys namespace
namespace FileSys {
// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format
const size_t FILENAME_LENGTH = 0x20C / 2;
// Structure of a directory entry, from
// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
const size_t FILENAME_LENGTH = 0x300;
struct Entry {
char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated)
std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated)
char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD)
std::array<char, 4>
extension; // 8.3 file extension (set to spaces for directories, null-terminated)
char unknown2; // unknown (always 0x01)
char unknown3; // unknown (0x00 or 0x08)
char is_directory; // directory flag
char is_hidden; // hidden flag
char is_archive; // archive flag
char is_read_only; // read-only flag
u64 file_size; // file size (for files only)
char filename[FILENAME_LENGTH];
INSERT_PADDING_BYTES(4);
EntryType type;
INSERT_PADDING_BYTES(3);
u64 file_size;
};
static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!");
static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry.");
static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry.");
static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry.");
static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry.");
static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!");
static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
class DirectoryBackend : NonCopyable {
public:
@@ -46,7 +40,10 @@ public:
* @param entries Buffer to read data into
* @return Number of entries listed
*/
virtual u32 Read(const u32 count, Entry* entries) = 0;
virtual u64 Read(const u64 count, Entry* entries) = 0;
/// Returns the number of entries still left to read.
virtual u64 GetEntryCount() const = 0;
/**
* Close the directory

View File

@@ -11,16 +11,43 @@
namespace FileSys {
static std::string ModeFlagsToString(Mode mode) {
std::string mode_str;
u32 mode_flags = static_cast<u32>(mode);
// Calculate the correct open mode for the file.
if ((mode_flags & static_cast<u32>(Mode::Read)) &&
(mode_flags & static_cast<u32>(Mode::Write))) {
if (mode_flags & static_cast<u32>(Mode::Append))
mode_str = "a+";
else
mode_str = "r+";
} else {
if (mode_flags & static_cast<u32>(Mode::Read))
mode_str = "r";
else if (mode_flags & static_cast<u32>(Mode::Append))
mode_str = "a";
else if (mode_flags & static_cast<u32>(Mode::Write))
mode_str = "w";
}
mode_str += "b";
return mode_str;
}
std::string Disk_FileSystem::GetName() const {
return "Disk";
}
ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
Mode mode) const {
ASSERT_MSG(mode == Mode::Read || mode == Mode::Write, "Other file modes are not supported");
// Calculate the correct open mode for the file.
std::string mode_str = ModeFlagsToString(mode);
std::string full_path = base_directory + path;
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode == Mode::Read ? "rb" : "wb");
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
if (!file->IsOpen()) {
return ERROR_PATH_NOT_FOUND;
@@ -75,8 +102,15 @@ ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const
return ResultCode(-1);
}
ResultCode Disk_FileSystem::CreateDirectory(const Path& path) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
// TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
std::string full_path = base_directory + path;
if (FileUtil::CreateDir(full_path)) {
return RESULT_SUCCESS;
}
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
@@ -88,8 +122,17 @@ ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& de
}
ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
const Path& path) const {
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<Disk_Directory>());
const std::string& path) const {
std::string full_path = base_directory + path;
if (!FileUtil::IsDirectory(full_path)) {
// TODO(Subv): Find the correct error code for this.
return ResultCode(-1);
}
auto directory = std::make_unique<Disk_Directory>(full_path);
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
}
u64 Disk_FileSystem::GetFreeSpaceSize() const {
@@ -103,8 +146,10 @@ ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& p
return ERROR_PATH_NOT_FOUND;
}
// TODO(Subv): Find out the EntryType values
UNIMPLEMENTED_MSG("Unimplemented GetEntryType");
if (FileUtil::IsDirectory(full_path))
return MakeResult(EntryType::Directory);
return MakeResult(EntryType::File);
}
ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
@@ -133,14 +178,50 @@ bool Disk_Storage::SetSize(const u64 size) const {
return false;
}
u32 Disk_Directory::Read(const u32 count, Entry* entries) {
LOG_WARNING(Service_FS, "(STUBBED) called");
return 0;
Disk_Directory::Disk_Directory(const std::string& path) : directory() {
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
directory.size = size;
directory.isDirectory = true;
children_iterator = directory.children.begin();
}
bool Disk_Directory::Close() const {
LOG_WARNING(Service_FS, "(STUBBED) called");
return true;
u64 Disk_Directory::Read(const u64 count, Entry* entries) {
u64 entries_read = 0;
while (entries_read < count && children_iterator != directory.children.cend()) {
const FileUtil::FSTEntry& file = *children_iterator;
const std::string& filename = file.virtualName;
Entry& entry = entries[entries_read];
LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
file.isDirectory);
// TODO(Link Mauve): use a proper conversion to UTF-16.
for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
entry.filename[j] = filename[j];
if (!filename[j])
break;
}
if (file.isDirectory) {
entry.file_size = 0;
entry.type = EntryType::Directory;
} else {
entry.file_size = file.size;
entry.type = EntryType::File;
}
++entries_read;
++children_iterator;
}
return entries_read;
}
u64 Disk_Directory::GetEntryCount() const {
// We convert the children iterator into a const_iterator to allow template argument deduction
// in std::distance.
std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
return std::distance(current, directory.children.end());
}
} // namespace FileSys

View File

@@ -30,9 +30,10 @@ public:
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
ResultCode CreateFile(const std::string& path, u64 size) const override;
ResultCode CreateDirectory(const Path& path) const override;
ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const override;
u64 GetFreeSpaceSize() const override;
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
@@ -59,8 +60,26 @@ private:
class Disk_Directory : public DirectoryBackend {
public:
u32 Read(const u32 count, Entry* entries) override;
bool Close() const override;
Disk_Directory(const std::string& path);
~Disk_Directory() override {
Close();
}
u64 Read(const u64 count, Entry* entries) override;
u64 GetEntryCount() const override;
bool Close() const override {
return true;
}
protected:
u32 total_entries_in_directory;
FileUtil::FSTEntry directory;
// We need to remember the last entry we returned, so a subsequent call to Read will continue
// from the next one. This iterator will always point to the next unread entry.
std::vector<FileUtil::FSTEntry>::iterator children_iterator;
};
} // namespace FileSys

View File

@@ -27,7 +27,7 @@ enum LowPathType : u32 {
Wchar = 4,
};
enum EntryType : u32 {
enum EntryType : u8 {
Directory = 0,
File = 1,
};
@@ -35,6 +35,7 @@ enum EntryType : u32 {
enum class Mode : u32 {
Read = 1,
Write = 2,
Append = 4,
};
class Path {
@@ -103,7 +104,7 @@ public:
* @param path Path relative to the archive
* @return Result of the operation
*/
virtual ResultCode CreateDirectory(const Path& path) const = 0;
virtual ResultCode CreateDirectory(const std::string& path) const = 0;
/**
* Delete a directory specified by its path
@@ -149,7 +150,8 @@ public:
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const = 0;
/**
* Get the free space

View File

@@ -55,7 +55,7 @@ ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::CreateDirectory(const Path& path) const {
ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).",
GetName().c_str());
// TODO(wwylele): Use correct error code
@@ -70,7 +70,8 @@ ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& d
}
ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
const Path& path) const {
const std::string& path) const {
LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
}

View File

@@ -36,9 +36,10 @@ public:
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
ResultCode CreateFile(const std::string& path, u64 size) const override;
ResultCode CreateDirectory(const Path& path) const override;
ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const override;
u64 GetFreeSpaceSize() const override;
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
@@ -70,7 +71,10 @@ private:
class ROMFSDirectory : public DirectoryBackend {
public:
u32 Read(const u32 count, Entry* entries) override {
u64 Read(const u64 count, Entry* entries) override {
return 0;
}
u64 GetEntryCount() const override {
return 0;
}
bool Close() const override {

View File

@@ -0,0 +1,40 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cinttypes>
#include <memory>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/disk_filesystem.h"
#include "core/file_sys/sdmc_factory.h"
namespace FileSys {
SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
// Create the SD Card directory if it doesn't already exist.
if (!FileUtil::IsDirectory(sd_directory)) {
FileUtil::CreateFullPath(sd_directory);
}
auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
}
ResultCode SDMC_Factory::Format(const Path& path) {
LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
// TODO(Subv): Find the right error code for this
return ResultCode(-1);
}
ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
// TODO(bunnei): Find the right error code for this
return ResultCode(-1);
}
} // namespace FileSys

View File

@@ -0,0 +1,31 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/filesystem.h"
#include "core/hle/result.h"
namespace FileSys {
/// File system interface to the SDCard archive
class SDMC_Factory final : public FileSystemFactory {
public:
explicit SDMC_Factory(std::string sd_directory);
std::string GetName() const override {
return "SDMC_Factory";
}
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
ResultCode Format(const Path& path) override;
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
private:
std::string sd_directory;
};
} // namespace FileSys

View File

@@ -151,12 +151,52 @@ private:
Kernel::SharedPtr<Kernel::Event> system_event;
};
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
public:
IAudioDevice() : ServiceFramework("IAudioDevice") {
static const FunctionInfo functions[] = {
{0x0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
{0x1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}};
RegisterHandlers(functions);
buffer_event =
Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
}
private:
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::RequestParser rp{ctx};
const std::string audio_interface = "AudioInterface";
ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1);
}
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::RequestParser rp{ctx};
f32 volume = static_cast<f32>(rp.Pop<u32>());
auto file_buffer = ctx.ReadBuffer();
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(RESULT_SUCCESS);
}
Kernel::SharedPtr<Kernel::Event> buffer_event;
};
AudRenU::AudRenU() : ServiceFramework("audren:u") {
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
{2, &AudRenU::GetAudioRenderersProcessMasterVolume, "GetAudioRenderersProcessMasterVolume"},
{3, nullptr, "SetAudioRenderersProcessMasterVolume"},
{2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
};
RegisterHandlers(functions);
}
@@ -179,12 +219,13 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
}
void AudRenU::GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(100);
LOG_WARNING(Service_Audio, "(STUBBED) called");
rb.PushIpcInterface<Audio::IAudioDevice>();
LOG_DEBUG(Service_Audio, "called");
}
} // namespace Audio

View File

@@ -21,7 +21,7 @@ public:
private:
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx);
void GetAudioDevice(Kernel::HLERequestContext& ctx);
};
} // namespace Audio

View File

@@ -6,6 +6,7 @@
#include "common/file_util.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
@@ -60,9 +61,13 @@ void RegisterFileSystems() {
filesystem_map.clear();
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
RegisterFileSystem(std::move(savedata), Type::SaveData);
auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
RegisterFileSystem(std::move(sdcard), Type::SDMC);
}
void InstallInterfaces(SM::ServiceManager& service_manager) {

View File

@@ -26,6 +26,7 @@ namespace FileSystem {
enum class Type {
RomFS = 1,
SaveData = 2,
SDMC = 3,
};
/**

View File

@@ -5,6 +5,7 @@
#include <cinttypes>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/storage.h"
#include "core/hle/ipc_helpers.h"
@@ -151,14 +152,66 @@ private:
}
};
class IDirectory final : public ServiceFramework<IDirectory> {
public:
explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
: ServiceFramework("IDirectory"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IDirectory::Read, "Read"},
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
};
RegisterHandlers(functions);
}
private:
std::unique_ptr<FileSys::DirectoryBackend> backend;
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 unk = rp.Pop<u64>();
LOG_DEBUG(Service_FS, "called, unk=0x%llx", unk);
// Calculate how many entries we can fit in the output buffer
u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
// Read the data from the Directory backend
std::vector<FileSys::Entry> entries(count_entries);
u64 read_entries = backend->Read(count_entries, entries.data());
// Convert the data into a byte array
std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
std::memcpy(output.data(), entries.data(), output.size());
// Write the data to memory
ctx.WriteBuffer(output);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(read_entries);
}
void GetEntryCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
u64 count = backend->GetEntryCount();
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(count);
}
};
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
: ServiceFramework("IFileSystem"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IFileSystem::CreateFile, "CreateFile"},
{2, &IFileSystem::CreateDirectory, "CreateDirectory"},
{7, &IFileSystem::GetEntryType, "GetEntryType"},
{8, &IFileSystem::OpenFile, "OpenFile"},
{9, &IFileSystem::OpenDirectory, "OpenDirectory"},
{10, &IFileSystem::Commit, "Commit"},
};
RegisterHandlers(functions);
@@ -182,6 +235,20 @@ public:
rb.Push(backend->CreateFile(name, size));
}
void CreateDirectory(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto file_buffer = ctx.ReadBuffer();
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
std::string name(file_buffer.begin(), end);
LOG_DEBUG(Service_FS, "called directory %s", name.c_str());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend->CreateDirectory(name));
}
void OpenFile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -208,6 +275,33 @@ public:
rb.PushIpcInterface<IFile>(std::move(file));
}
void OpenDirectory(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto file_buffer = ctx.ReadBuffer();
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
std::string name(file_buffer.begin(), end);
// TODO(Subv): Implement this filter.
u32 filter_flags = rp.Pop<u32>();
LOG_DEBUG(Service_FS, "called directory %s filter %u", name.c_str(), filter_flags);
auto result = backend->OpenDirectory(name);
if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
return;
}
auto directory = std::move(result.Unwrap());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDirectory>(std::move(directory));
}
void GetEntryType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -274,10 +368,14 @@ void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called");
LOG_DEBUG(Service_FS, "called");
IPC::ResponseBuilder rb{ctx, 2};
FileSys::Path unused;
auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
}
void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {

View File

@@ -193,7 +193,7 @@ public:
{121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault,
"SetNpadJoyAssignmentModeSingleByDefault"},
{124, nullptr, "SetNpadJoyAssignmentModeDual"},
{124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
{200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
@@ -315,6 +315,12 @@ private:
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);

View File

@@ -33,6 +33,7 @@ enum class LoadState : u32 {
PL_U::PL_U() : ServiceFramework("pl:u") {
static const FunctionInfo functions[] = {
{0, &PL_U::RequestLoad, "RequestLoad"},
{1, &PL_U::GetLoadState, "GetLoadState"},
{2, &PL_U::GetSize, "GetSize"},
{3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
@@ -54,6 +55,15 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
}
}
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 shared_font_type{rp.Pop<u32>()};
LOG_DEBUG(Service_NS, "called, shared_font_type=%d", shared_font_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 font_id{rp.Pop<u32>()};

View File

@@ -17,6 +17,7 @@ public:
~PL_U() = default;
private:
void RequestLoad(Kernel::HLERequestContext& ctx);
void GetLoadState(Kernel::HLERequestContext& ctx);
void GetSize(Kernel::HLERequestContext& ctx);
void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);

View File

@@ -34,6 +34,8 @@
#include "core/hle/service/sm/controller.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/sockets/sockets.h"
#include "core/hle/service/spl/module.h"
#include "core/hle/service/ssl/ssl.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/vi/vi.h"
@@ -190,6 +192,8 @@ void Init() {
Nvidia::InstallInterfaces(*SM::g_service_manager);
PCTL::InstallInterfaces(*SM::g_service_manager);
Sockets::InstallInterfaces(*SM::g_service_manager);
SPL::InstallInterfaces(*SM::g_service_manager);
SSL::InstallInterfaces(*SM::g_service_manager);
Time::InstallInterfaces(*SM::g_service_manager);
VI::InstallInterfaces(*SM::g_service_manager, nv_flinger);
Set::InstallInterfaces(*SM::g_service_manager);

View File

@@ -3,12 +3,12 @@
// Refer to the license.txt file included.
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/sockets/bsd_u.h"
#include "core/hle/service/sockets/bsd.h"
namespace Service {
namespace Sockets {
void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) {
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
@@ -17,7 +17,7 @@ void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) {
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
@@ -26,7 +26,7 @@ void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
void BSD_U::Socket(Kernel::HLERequestContext& ctx) {
void BSD::Socket(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 domain = rp.Pop<u32>();
@@ -44,7 +44,7 @@ void BSD_U::Socket(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
void BSD_U::Connect(Kernel::HLERequestContext& ctx) {
void BSD::Connect(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -54,7 +54,7 @@ void BSD_U::Connect(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
void BSD_U::SendTo(Kernel::HLERequestContext& ctx) {
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -64,7 +64,7 @@ void BSD_U::SendTo(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
void BSD_U::Close(Kernel::HLERequestContext& ctx) {
void BSD::Close(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -74,13 +74,15 @@ void BSD_U::Close(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
BSD_U::BSD_U() : ServiceFramework("bsd:u") {
static const FunctionInfo functions[] = {{0, &BSD_U::RegisterClient, "RegisterClient"},
{1, &BSD_U::StartMonitoring, "StartMonitoring"},
{2, &BSD_U::Socket, "Socket"},
{11, &BSD_U::SendTo, "SendTo"},
{14, &BSD_U::Connect, "Connect"},
{26, &BSD_U::Close, "Close"}};
BSD::BSD(const char* name) : ServiceFramework(name) {
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
{1, &BSD::StartMonitoring, "StartMonitoring"},
{2, &BSD::Socket, "Socket"},
{11, &BSD::SendTo, "SendTo"},
{14, &BSD::Connect, "Connect"},
{26, &BSD::Close, "Close"},
};
RegisterHandlers(functions);
}

View File

@@ -10,10 +10,10 @@
namespace Service {
namespace Sockets {
class BSD_U final : public ServiceFramework<BSD_U> {
class BSD final : public ServiceFramework<BSD> {
public:
BSD_U();
~BSD_U() = default;
explicit BSD(const char* name);
~BSD() = default;
private:
void RegisterClient(Kernel::HLERequestContext& ctx);

View File

@@ -0,0 +1,34 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/sockets/nsd.h"
namespace Service {
namespace Sockets {
NSD::NSD(const char* name) : ServiceFramework(name) {
static const FunctionInfo functions[] = {
{10, nullptr, "GetSettingName"},
{11, nullptr, "GetEnvironmentIdentifier"},
{12, nullptr, "GetDeviceId"},
{13, nullptr, "DeleteSettings"},
{14, nullptr, "ImportSettings"},
{20, nullptr, "Resolve"},
{21, nullptr, "ResolveEx"},
{30, nullptr, "GetNasServiceSetting"},
{31, nullptr, "GetNasServiceSettingEx"},
{40, nullptr, "GetNasRequestFqdn"},
{41, nullptr, "GetNasRequestFqdnEx"},
{42, nullptr, "GetNasApiFqdn"},
{43, nullptr, "GetNasApiFqdnEx"},
{50, nullptr, "GetCurrentSetting"},
{60, nullptr, "ReadSaveDataFromFsForTest"},
{61, nullptr, "WriteSaveDataToFsForTest"},
{62, nullptr, "DeleteSaveDataOfFsForTest"},
};
RegisterHandlers(functions);
}
} // namespace Sockets
} // namespace Service

View File

@@ -0,0 +1,20 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
namespace Service {
namespace Sockets {
class NSD final : public ServiceFramework<NSD> {
public:
explicit NSD(const char* name);
~NSD() = default;
};
} // namespace Sockets
} // namespace Service

View File

@@ -19,16 +19,18 @@ void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
}
SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
static const FunctionInfo functions[] = {{0, nullptr, "SetDnsAddressesPrivate"},
{1, nullptr, "GetDnsAddressPrivate"},
{2, nullptr, "GetHostByName"},
{3, nullptr, "GetHostByAddr"},
{4, nullptr, "GetHostStringError"},
{5, nullptr, "GetGaiStringError"},
{6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
{7, nullptr, "GetNameInfo"},
{8, nullptr, "RequestCancelHandle"},
{9, nullptr, "CancelSocketCall"}};
static const FunctionInfo functions[] = {
{0, nullptr, "SetDnsAddressesPrivate"},
{1, nullptr, "GetDnsAddressPrivate"},
{2, nullptr, "GetHostByName"},
{3, nullptr, "GetHostByAddr"},
{4, nullptr, "GetHostStringError"},
{5, nullptr, "GetGaiStringError"},
{6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
{7, nullptr, "GetNameInfo"},
{8, nullptr, "RequestCancelHandle"},
{9, nullptr, "CancelSocketCall"},
};
RegisterHandlers(functions);
}

View File

@@ -12,7 +12,7 @@ namespace Sockets {
class SFDNSRES final : public ServiceFramework<SFDNSRES> {
public:
SFDNSRES();
explicit SFDNSRES();
~SFDNSRES() = default;
private:

View File

@@ -2,7 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/sockets/bsd_u.h"
#include "core/hle/service/sockets/bsd.h"
#include "core/hle/service/sockets/nsd.h"
#include "core/hle/service/sockets/sfdnsres.h"
#include "core/hle/service/sockets/sockets.h"
@@ -10,7 +11,10 @@ namespace Service {
namespace Sockets {
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<BSD_U>()->InstallAsService(service_manager);
std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager);
std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager);
std::make_shared<SFDNSRES>()->InstallAsService(service_manager);
}

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/spl/csrng.h"
namespace Service {
namespace SPL {
CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") {
static const FunctionInfo functions[] = {
{0, &CSRNG::GetRandomBytes, "GetRandomBytes"},
};
RegisterHandlers(functions);
}
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/spl/module.h"
namespace Service {
namespace SPL {
class CSRNG final : public Module::Interface {
public:
explicit CSRNG(std::shared_ptr<Module> module);
};
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,42 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <vector>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/spl/csrng.h"
#include "core/hle/service/spl/module.h"
#include "core/hle/service/spl/spl.h"
namespace Service {
namespace SPL {
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)) {}
void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
size_t size = ctx.GetWriteBufferSize();
std::vector<u8> data(size);
std::generate(data.begin(), data.end(), std::rand);
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_SPL, "called");
}
void InstallInterfaces(SM::ServiceManager& service_manager) {
auto module = std::make_shared<Module>();
std::make_shared<CSRNG>(module)->InstallAsService(service_manager);
std::make_shared<SPL>(module)->InstallAsService(service_manager);
}
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,29 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service {
namespace SPL {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> module, const char* name);
void GetRandomBytes(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> module;
};
};
/// Registers all SPL services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,41 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/spl/spl.h"
namespace Service {
namespace SPL {
SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") {
static const FunctionInfo functions[] = {
{0, nullptr, "GetConfig"},
{1, nullptr, "UserExpMod"},
{2, nullptr, "GenerateAesKek"},
{3, nullptr, "LoadAesKey"},
{4, nullptr, "GenerateAesKey"},
{5, nullptr, "SetConfig"},
{7, &SPL::GetRandomBytes, "GetRandomBytes"},
{9, nullptr, "LoadSecureExpModKey"},
{10, nullptr, "SecureExpMod"},
{11, nullptr, "IsDevelopment"},
{12, nullptr, "GenerateSpecificAesKey"},
{13, nullptr, "DecryptPrivk"},
{14, nullptr, "DecryptAesKey"},
{15, nullptr, "DecryptAesCtr"},
{16, nullptr, "ComputeCmac"},
{17, nullptr, "LoadRsaOaepKey"},
{18, nullptr, "UnwrapRsaOaepWrappedTitleKey"},
{19, nullptr, "LoadTitleKey"},
{20, nullptr, "UnwrapAesWrappedTitleKey"},
{21, nullptr, "LockAesEngine"},
{22, nullptr, "UnlockAesEngine"},
{23, nullptr, "GetSplWaitEvent"},
{24, nullptr, "SetSharedData"},
{25, nullptr, "GetSharedData"},
};
RegisterHandlers(functions);
}
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/spl/module.h"
namespace Service {
namespace SPL {
class SPL final : public Module::Interface {
public:
explicit SPL(std::shared_ptr<Module> module);
};
} // namespace SPL
} // namespace Service

View File

@@ -0,0 +1,17 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/ssl/ssl.h"
namespace Service {
namespace SSL {
SSL::SSL() : ServiceFramework("ssl") {}
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<SSL>()->InstallAsService(service_manager);
}
} // namespace SSL
} // namespace Service

View File

@@ -0,0 +1,22 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service {
namespace SSL {
class SSL final : public ServiceFramework<SSL> {
public:
explicit SSL();
~SSL() = default;
};
/// Registers all SSL services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
} // namespace SSL
} // namespace Service

View File

@@ -23,7 +23,6 @@
namespace Memory {
static std::array<u8, Memory::VRAM_SIZE> vram;
static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram;
static PageTable* current_page_table = nullptr;
@@ -247,7 +246,6 @@ u8* GetPhysicalPointer(PAddr address) {
{IO_AREA_PADDR, IO_AREA_SIZE},
{DSP_RAM_PADDR, DSP_RAM_SIZE},
{FCRAM_PADDR, FCRAM_N3DS_SIZE},
{N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE},
};
const auto area =
@@ -286,9 +284,6 @@ u8* GetPhysicalPointer(PAddr address) {
}
ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address");
break;
case N3DS_EXTRA_RAM_PADDR:
target_pointer = n3ds_extra_ram.data() + offset_into_region;
break;
default:
UNREACHABLE();
}
@@ -649,8 +644,6 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
return addr - DSP_RAM_VADDR + DSP_RAM_PADDR;
} else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) {
return addr - IO_AREA_VADDR + IO_AREA_PADDR;
} else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) {
return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
}
return boost::none;
@@ -677,8 +670,6 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
return addr - DSP_RAM_PADDR + DSP_RAM_VADDR;
} else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) {
return addr - IO_AREA_PADDR + IO_AREA_VADDR;
} else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) {
return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
}
return boost::none;

View File

@@ -101,12 +101,6 @@ enum : PAddr {
VRAM_SIZE = 0x00600000, ///< VRAM size (6MB)
VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE,
/// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by
/// applications and system modules if mapped via the ExHeader.
N3DS_EXTRA_RAM_PADDR = 0x1F000000,
N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB)
N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE,
/// DSP memory
DSP_RAM_PADDR = 0x1FF00000,
DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB)
@@ -122,7 +116,6 @@ enum : PAddr {
FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
@@ -138,10 +131,6 @@ enum : VAddr {
LINEAR_HEAP_SIZE = 0x08000000,
LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE,
/// Maps 1:1 to New 3DS additional memory
N3DS_EXTRA_RAM_VADDR = 0x1E800000,
N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE,
/// Maps 1:1 to the IO register area.
IO_AREA_VADDR = 0x1EC00000,
IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE,

View File

@@ -1,6 +1,8 @@
add_library(video_core STATIC
command_processor.cpp
command_processor.h
debug_utils/debug_utils.cpp
debug_utils/debug_utils.h
engines/fermi_2d.cpp
engines/fermi_2d.h
engines/maxwell_3d.cpp
@@ -31,6 +33,9 @@ add_library(video_core STATIC
renderer_opengl/gl_stream_buffer.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
textures/decoders.cpp
textures/decoders.h
textures/texture.h
utils.h
video_core.cpp
video_core.h

View File

@@ -0,0 +1,64 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <algorithm>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <map>
#include <mutex>
#include <string>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/color.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/vector_math.h"
#include "video_core/debug_utils/debug_utils.h"
namespace Tegra {
void DebugContext::DoOnEvent(Event event, void* data) {
{
std::unique_lock<std::mutex> lock(breakpoint_mutex);
// TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
// show on debug widgets
// TODO: Should stop the CPU thread here once we multithread emulation.
active_breakpoint = event;
at_breakpoint = true;
// Tell all observers that we hit a breakpoint
for (auto& breakpoint_observer : breakpoint_observers) {
breakpoint_observer->OnMaxwellBreakPointHit(event, data);
}
// Wait until another thread tells us to Resume()
resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; });
}
}
void DebugContext::Resume() {
{
std::lock_guard<std::mutex> lock(breakpoint_mutex);
// Tell all observers that we are about to resume
for (auto& breakpoint_observer : breakpoint_observers) {
breakpoint_observer->OnMaxwellResume();
}
// Resume the waiting thread (i.e. OnEvent())
at_breakpoint = false;
}
resume_from_breakpoint.notify_one();
}
} // namespace Tegra

View File

@@ -0,0 +1,163 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <array>
#include <condition_variable>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "common/vector_math.h"
namespace Tegra {
class DebugContext {
public:
enum class Event {
FirstEvent = 0,
MaxwellCommandLoaded = FirstEvent,
MaxwellCommandProcessed,
IncomingPrimitiveBatch,
FinishedPrimitiveBatch,
NumEvents
};
/**
* Inherit from this class to be notified of events registered to some debug context.
* Most importantly this is used for our debugger GUI.
*
* To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
* @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
* access
* @todo Evaluate an alternative interface, in which there is only one managing observer and
* multiple child observers running (by design) on the same thread.
*/
class BreakPointObserver {
public:
/// Constructs the object such that it observes events of the given DebugContext.
BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
: context_weak(debug_context) {
std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
debug_context->breakpoint_observers.push_back(this);
}
virtual ~BreakPointObserver() {
auto context = context_weak.lock();
if (context) {
std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
context->breakpoint_observers.remove(this);
// If we are the last observer to be destroyed, tell the debugger context that
// it is free to continue. In particular, this is required for a proper yuzu
// shutdown, when the emulation thread is waiting at a breakpoint.
if (context->breakpoint_observers.empty())
context->Resume();
}
}
/**
* Action to perform when a breakpoint was reached.
* @param event Type of event which triggered the breakpoint
* @param data Optional data pointer (if unused, this is a nullptr)
* @note This function will perform nothing unless it is overridden in the child class.
*/
virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
/**
* Action to perform when emulation is resumed from a breakpoint.
* @note This function will perform nothing unless it is overridden in the child class.
*/
virtual void OnMaxwellResume() {}
protected:
/**
* Weak context pointer. This need not be valid, so when requesting a shared_ptr via
* context_weak.lock(), always compare the result against nullptr.
*/
std::weak_ptr<DebugContext> context_weak;
};
/**
* Simple structure defining a breakpoint state
*/
struct BreakPoint {
bool enabled = false;
};
/**
* Static constructor used to create a shared_ptr of a DebugContext.
*/
static std::shared_ptr<DebugContext> Construct() {
return std::shared_ptr<DebugContext>(new DebugContext);
}
/**
* Used by the emulation core when a given event has happened. If a breakpoint has been set
* for this event, OnEvent calls the event handlers of the registered breakpoint observers.
* The current thread then is halted until Resume() is called from another thread (or until
* emulation is stopped).
* @param event Event which has happened
* @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
* Resume() is called.
*/
void OnEvent(Event event, void* data) {
// This check is left in the header to allow the compiler to inline it.
if (!breakpoints[(int)event].enabled)
return;
// For the rest of event handling, call a separate function.
DoOnEvent(event, data);
}
void DoOnEvent(Event event, void* data);
/**
* Resume from the current breakpoint.
* @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
* Calling from any other thread is safe.
*/
void Resume();
/**
* Delete all set breakpoints and resume emulation.
*/
void ClearBreakpoints() {
for (auto& bp : breakpoints) {
bp.enabled = false;
}
Resume();
}
// TODO: Evaluate if access to these members should be hidden behind a public interface.
std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
Event active_breakpoint;
bool at_breakpoint = false;
private:
/**
* Private default constructor to make sure people always construct this through Construct()
* instead.
*/
DebugContext() = default;
/// Mutex protecting current breakpoint state and the observer list.
std::mutex breakpoint_mutex;
/// Used by OnEvent to wait for resumption.
std::condition_variable resume_from_breakpoint;
/// List of registered observers
std::list<BreakPointObserver*> breakpoint_observers;
};
} // namespace Tegra

View File

@@ -2,8 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cinttypes>
#include "common/assert.h"
#include "core/core.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace Tegra {
namespace Engines {
@@ -46,6 +51,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
// It is an error to write to a register other than the current macro's ARG register before it
// has finished execution.
if (executing_macro != 0) {
@@ -72,6 +79,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
return;
}
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr);
}
regs.reg_array[method] = value;
#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32))
@@ -137,6 +148,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
}
#undef MAXWELL3D_REG_INDEX
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr);
}
}
void Maxwell3D::ProcessQueryGet() {
@@ -160,6 +175,15 @@ void Maxwell3D::ProcessQueryGet() {
void Maxwell3D::DrawArrays() {
LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring");
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
}
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
}
}
void Maxwell3D::BindTextureInfoBuffer(const std::vector<u32>& parameters) {
@@ -270,5 +294,50 @@ void Maxwell3D::ProcessCBData(u32 value) {
regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
}
std::vector<Texture::TICEntry> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) {
std::vector<Texture::TICEntry> textures;
auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)];
auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index];
ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
GPUVAddr tic_base_address = regs.tic.TICAddress();
GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size;
// Offset into the texture constbuffer where the texture info begins.
static constexpr size_t TextureInfoOffset = 0x20;
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
current_texture < tex_info_buffer_end; current_texture += 4) {
Texture::TextureHandle tex_info{
Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))};
if (tex_info.tic_id != 0 || tex_info.tsc_id != 0) {
GPUVAddr tic_address_gpu =
tic_base_address + tex_info.tic_id * sizeof(Texture::TICEntry);
VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu);
Texture::TICEntry tic_entry;
Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
auto r_type = tic_entry.r_type.Value();
auto g_type = tic_entry.g_type.Value();
auto b_type = tic_entry.b_type.Value();
auto a_type = tic_entry.a_type.Value();
// TODO(Subv): Different data types for separate components are not supported
ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
auto format = tic_entry.format.Value();
textures.push_back(tic_entry);
}
}
return textures;
}
} // namespace Engines
} // namespace Tegra

View File

@@ -12,6 +12,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/memory_manager.h"
#include "video_core/textures/texture.h"
namespace Tegra {
namespace Engines {
@@ -21,18 +22,13 @@ public:
explicit Maxwell3D(MemoryManager& memory_manager);
~Maxwell3D() = default;
/// Write the value to the register identified by method.
void WriteReg(u32 method, u32 value, u32 remaining_params);
/// Uploads the code for a GPU macro program associated with the specified entry.
void SubmitMacroCode(u32 entry, std::vector<u32> code);
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
struct Regs {
static constexpr size_t NUM_REGS = 0xE36;
static constexpr size_t NumRenderTargets = 8;
static constexpr size_t NumViewports = 16;
static constexpr size_t NumCBData = 16;
static constexpr size_t NumVertexArrays = 32;
static constexpr size_t NumVertexAttributes = 32;
@@ -186,7 +182,22 @@ public:
}
} rt[NumRenderTargets];
INSERT_PADDING_WORDS(0xDD);
INSERT_PADDING_WORDS(0x80);
struct {
union {
BitField<0, 16, u32> x;
BitField<16, 16, u32> width;
};
union {
BitField<0, 16, u32> y;
BitField<16, 16, u32> height;
};
float depth_range_near;
float depth_range_far;
} viewport[NumViewports];
INSERT_PADDING_WORDS(0x1D);
struct {
u32 first;
@@ -414,6 +425,15 @@ public:
State state{};
/// Write the value to the register identified by method.
void WriteReg(u32 method, u32 value, u32 remaining_params);
/// Uploads the code for a GPU macro program associated with the specified entry.
void SubmitMacroCode(u32 entry, std::vector<u32> code);
/// Returns a list of enabled textures for the specified shader stage.
std::vector<Texture::TICEntry> GetStageTextures(Regs::ShaderStage stage);
private:
MemoryManager& memory_manager;
@@ -462,6 +482,7 @@ private:
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(rt, 0x200);
ASSERT_REG_POSITION(viewport, 0x300);
ASSERT_REG_POSITION(vertex_buffer, 0x35D);
ASSERT_REG_POSITION(zeta, 0x3F8);
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);

View File

@@ -18,4 +18,8 @@ GPU::GPU() {
GPU::~GPU() = default;
const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
return *maxwell_3d;
}
} // namespace Tegra

View File

@@ -13,6 +13,12 @@
namespace Tegra {
enum class RenderTargetFormat {
RGBA8_UNORM = 0xD5,
};
class DebugContext;
/**
* Struct describing framebuffer configuration
*/
@@ -66,6 +72,9 @@ public:
/// Processes a command list stored at the specified address in GPU memory.
void ProcessCommandList(GPUVAddr address, u32 size);
/// Returns a reference to the Maxwell3D GPU engine.
const Engines::Maxwell3D& Get3DEngine() const;
std::unique_ptr<MemoryManager> memory_manager;
Engines::Maxwell3D& Maxwell3D() {

View File

@@ -0,0 +1,105 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace Tegra {
namespace Texture {
/**
* Calculates the offset of an (x, y) position within a swizzled texture.
* Taken from the Tegra X1 TRM.
*/
static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) {
u32 image_width_in_gobs = image_width * bytes_per_pixel / 64;
u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
(x * bytes_per_pixel / 64) * 512 * block_height +
(y % (8 * block_height) / 8) * 512;
x *= bytes_per_pixel;
u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
(y % 2) * 16 + (x % 16);
return address;
}
static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
u32 block_height) {
u8* data_ptrs[2];
for (unsigned y = 0; y < height; ++y) {
for (unsigned x = 0; x < width; ++x) {
u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height);
u32 pixel_index = (x + y * width) * out_bytes_per_pixel;
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = &unswizzled_data[pixel_index];
std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
}
}
}
u32 BytesPerPixel(TextureFormat format) {
switch (format) {
case TextureFormat::DXT1:
// In this case a 'pixel' actually refers to a 4x4 tile.
return 8;
case TextureFormat::A8R8G8B8:
return 4;
default:
UNIMPLEMENTED_MSG("Format not implemented");
break;
}
}
std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) {
u8* data = Memory::GetPointer(address);
u32 bytes_per_pixel = BytesPerPixel(format);
static constexpr u32 DefaultBlockHeight = 16;
std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
switch (format) {
case TextureFormat::DXT1:
// In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values.
CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
unswizzled_data.data(), true, DefaultBlockHeight);
break;
case TextureFormat::A8R8G8B8:
CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
unswizzled_data.data(), true, DefaultBlockHeight);
break;
default:
UNIMPLEMENTED_MSG("Format not implemented");
break;
}
return unswizzled_data;
}
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height) {
std::vector<u8> rgba_data;
// TODO(Subv): Implement.
switch (format) {
case TextureFormat::DXT1:
case TextureFormat::A8R8G8B8:
// TODO(Subv): For the time being just forward the same data without any decoding.
rgba_data = texture_data;
break;
default:
UNIMPLEMENTED_MSG("Format not implemented");
break;
}
return rgba_data;
}
} // namespace Texture
} // namespace Tegra

View File

@@ -0,0 +1,26 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_types.h"
#include "video_core/textures/texture.h"
namespace Tegra {
namespace Texture {
/**
* Unswizzles a swizzled texture without changing its format.
*/
std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height);
/**
* Decodes an unswizzled texture into a A8R8G8B8 texture.
*/
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height);
} // namespace Texture
} // namespace Tegra

View File

@@ -0,0 +1,61 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/memory_manager.h"
namespace Tegra {
namespace Texture {
enum class TextureFormat : u32 {
A8R8G8B8 = 8,
DXT1 = 0x24,
};
union TextureHandle {
u32 raw;
BitField<0, 20, u32> tic_id;
BitField<20, 12, u32> tsc_id;
};
struct TICEntry {
union {
u32 raw;
BitField<0, 7, TextureFormat> format;
BitField<7, 3, u32> r_type;
BitField<10, 3, u32> g_type;
BitField<13, 3, u32> b_type;
BitField<16, 3, u32> a_type;
};
u32 address_low;
u16 address_high;
INSERT_PADDING_BYTES(6);
u16 width_minus_1;
INSERT_PADDING_BYTES(2);
u16 height_minus_1;
INSERT_PADDING_BYTES(10);
GPUVAddr Address() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
}
u32 Width() const {
return width_minus_1 + 1;
}
u32 Height() const {
return height_minus_1 + 1;
}
};
static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
/// Returns the number of bytes per pixel of the input texture format.
u32 BytesPerPixel(TextureFormat format);
} // namespace Texture
} // namespace Tegra

View File

@@ -23,6 +23,13 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_system.cpp
configuration/configure_system.h
debugger/graphics/graphics_breakpoint_observer.cpp
debugger/graphics/graphics_breakpoint_observer.h
debugger/graphics/graphics_breakpoints.cpp
debugger/graphics/graphics_breakpoints.h
debugger/graphics/graphics_breakpoints_p.h
debugger/graphics/graphics_surface.cpp
debugger/graphics/graphics_surface.h
debugger/profiler.cpp
debugger/profiler.h
debugger/registers.cpp

View File

@@ -0,0 +1,27 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMetaType>
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context,
const QString& title, QWidget* parent)
: QDockWidget(title, parent), BreakPointObserver(debug_context) {
qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
// care of delaying its handling to the GUI thread.
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
}
void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) {
emit BreakPointHit(event, data);
}
void BreakPointObserverDock::OnMaxwellResume() {
emit Resumed();
}

View File

@@ -0,0 +1,33 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDockWidget>
#include "video_core/debug_utils/debug_utils.h"
/**
* Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots.
* This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while
* the widget usually wants to perform reactions in the GUI thread.
*/
class BreakPointObserverDock : public QDockWidget,
protected Tegra::DebugContext::BreakPointObserver {
Q_OBJECT
public:
BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title,
QWidget* parent = nullptr);
void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
void OnMaxwellResume() override;
private slots:
virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0;
virtual void OnResumed() = 0;
signals:
void Resumed();
void BreakPointHit(Tegra::DebugContext::Event event, void* data);
};

View File

@@ -0,0 +1,212 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QLabel>
#include <QMetaType>
#include <QPushButton>
#include <QTreeView>
#include <QVBoxLayout>
#include "common/assert.h"
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
#include "yuzu/debugger/graphics/graphics_breakpoints_p.h"
BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context,
QObject* parent)
: QAbstractListModel(parent), context_weak(debug_context),
at_breakpoint(debug_context->at_breakpoint),
active_breakpoint(debug_context->active_breakpoint) {}
int BreakPointModel::columnCount(const QModelIndex& parent) const {
return 1;
}
int BreakPointModel::rowCount(const QModelIndex& parent) const {
return static_cast<int>(Tegra::DebugContext::Event::NumEvents);
}
QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
switch (role) {
case Qt::DisplayRole: {
if (index.column() == 0) {
static const std::map<Tegra::DebugContext::Event, QString> map = {
{Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
{Tegra::DebugContext::Event::MaxwellCommandProcessed,
tr("Maxwell command processed")},
{Tegra::DebugContext::Event::IncomingPrimitiveBatch,
tr("Incoming primitive batch")},
{Tegra::DebugContext::Event::FinishedPrimitiveBatch,
tr("Finished primitive batch")},
};
DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents));
return (map.find(event) != map.end()) ? map.at(event) : QString();
}
break;
}
case Qt::CheckStateRole: {
if (index.column() == 0)
return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
break;
}
case Qt::BackgroundRole: {
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
return QBrush(QColor(0xE0, 0xE0, 0x10));
}
break;
}
case Role_IsEnabled: {
auto context = context_weak.lock();
return context && context->breakpoints[(int)event].enabled;
}
default:
break;
}
return QVariant();
}
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
if (!index.isValid())
return 0;
Qt::ItemFlags flags = Qt::ItemIsEnabled;
if (index.column() == 0)
flags |= Qt::ItemIsUserCheckable;
return flags;
}
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
switch (role) {
case Qt::CheckStateRole: {
if (index.column() != 0)
return false;
auto context = context_weak.lock();
if (!context)
return false;
context->breakpoints[(int)event].enabled = value == Qt::Checked;
QModelIndex changed_index = createIndex(index.row(), 0);
emit dataChanged(changed_index, changed_index);
return true;
}
}
return false;
}
void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) {
auto context = context_weak.lock();
if (!context)
return;
active_breakpoint = context->active_breakpoint;
at_breakpoint = context->at_breakpoint;
emit dataChanged(createIndex(static_cast<int>(event), 0),
createIndex(static_cast<int>(event), 0));
}
void BreakPointModel::OnResumed() {
auto context = context_weak.lock();
if (!context)
return;
at_breakpoint = context->at_breakpoint;
emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
createIndex(static_cast<int>(active_breakpoint), 0));
active_breakpoint = context->active_breakpoint;
}
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
debug_context) {
setObjectName("TegraBreakPointsWidget");
status_text = new QLabel(tr("Emulation running"));
resume_button = new QPushButton(tr("Resume"));
resume_button->setEnabled(false);
breakpoint_model = new BreakPointModel(debug_context, this);
breakpoint_list = new QTreeView;
breakpoint_list->setRootIsDecorated(false);
breakpoint_list->setHeaderHidden(true);
breakpoint_list->setModel(breakpoint_model);
qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
SLOT(OnItemDoubleClicked(const QModelIndex&)));
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model,
SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection);
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
QWidget* main_widget = new QWidget;
auto main_layout = new QVBoxLayout;
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(status_text);
sub_layout->addWidget(resume_button);
main_layout->addLayout(sub_layout);
}
main_layout->addWidget(breakpoint_list);
main_widget->setLayout(main_layout);
setWidget(main_widget);
}
void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) {
// Process in GUI thread
emit BreakPointHit(event, data);
}
void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
status_text->setText(tr("Emulation halted at breakpoint"));
resume_button->setEnabled(true);
}
void GraphicsBreakPointsWidget::OnMaxwellResume() {
// Process in GUI thread
emit Resumed();
}
void GraphicsBreakPointsWidget::OnResumed() {
status_text->setText(tr("Emulation running"));
resume_button->setEnabled(false);
}
void GraphicsBreakPointsWidget::OnResumeRequested() {
if (auto context = context_weak.lock())
context->Resume();
}
void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
if (!index.isValid())
return;
QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
QVariant new_state = Qt::Unchecked;
if (enabled == Qt::Unchecked)
new_state = Qt::Checked;
breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
}

View File

@@ -0,0 +1,46 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDockWidget>
#include "video_core/debug_utils/debug_utils.h"
class QLabel;
class QPushButton;
class QTreeView;
class BreakPointModel;
class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver {
Q_OBJECT
using Event = Tegra::DebugContext::Event;
public:
explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
QWidget* parent = nullptr);
void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
void OnMaxwellResume() override;
public slots:
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data);
void OnItemDoubleClicked(const QModelIndex&);
void OnResumeRequested();
void OnResumed();
signals:
void Resumed();
void BreakPointHit(Tegra::DebugContext::Event event, void* data);
void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
QLabel* status_text;
QPushButton* resume_button;
BreakPointModel* breakpoint_model;
QTreeView* breakpoint_list;
};

View File

@@ -0,0 +1,36 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QAbstractListModel>
#include "video_core/debug_utils/debug_utils.h"
class BreakPointModel : public QAbstractListModel {
Q_OBJECT
public:
enum {
Role_IsEnabled = Qt::UserRole,
};
BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent);
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
public slots:
void OnBreakPointHit(Tegra::DebugContext::Event event);
void OnResumed();
private:
std::weak_ptr<Tegra::DebugContext> context_weak;
bool at_breakpoint;
Tegra::DebugContext::Event active_breakpoint;
};

View File

@@ -0,0 +1,452 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QBoxLayout>
#include <QComboBox>
#include <QDebug>
#include <QFileDialog>
#include <QLabel>
#include <QMouseEvent>
#include <QPushButton>
#include <QScrollArea>
#include <QSpinBox>
#include "core/core.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
#include "video_core/utils.h"
#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/util/spinbox.h"
static Tegra::Texture::TextureFormat ConvertToTextureFormat(
Tegra::RenderTargetFormat render_target_format) {
switch (render_target_format) {
case Tegra::RenderTargetFormat::RGBA8_UNORM:
return Tegra::Texture::TextureFormat::A8R8G8B8;
default:
UNIMPLEMENTED_MSG("Unimplemented RT format");
}
}
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
: QLabel(parent), surface_widget(surface_widget_) {}
SurfacePicture::~SurfacePicture() {}
void SurfacePicture::mousePressEvent(QMouseEvent* event) {
// Only do something while the left mouse button is held down
if (!(event->buttons() & Qt::LeftButton))
return;
if (pixmap() == nullptr)
return;
if (surface_widget)
surface_widget->Pick(event->x() * pixmap()->width() / width(),
event->y() * pixmap()->height() / height());
}
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
// We also want to handle the event if the user moves the mouse while holding down the LMB
mousePressEvent(event);
}
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
QWidget* parent)
: BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
surface_source(Source::RenderTarget0) {
setObjectName("MaxwellSurface");
surface_source_list = new QComboBox;
surface_source_list->addItem(tr("Render Target 0"));
surface_source_list->addItem(tr("Render Target 1"));
surface_source_list->addItem(tr("Render Target 2"));
surface_source_list->addItem(tr("Render Target 3"));
surface_source_list->addItem(tr("Render Target 4"));
surface_source_list->addItem(tr("Render Target 5"));
surface_source_list->addItem(tr("Render Target 6"));
surface_source_list->addItem(tr("Render Target 7"));
surface_source_list->addItem(tr("Z Buffer"));
surface_source_list->addItem(tr("Custom"));
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
surface_address_control = new CSpinBox;
surface_address_control->SetBase(16);
surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
surface_address_control->SetPrefix("0x");
unsigned max_dimension = 16384; // TODO: Find actual maximum
surface_width_control = new QSpinBox;
surface_width_control->setRange(0, max_dimension);
surface_height_control = new QSpinBox;
surface_height_control->setRange(0, max_dimension);
surface_picker_x_control = new QSpinBox;
surface_picker_x_control->setRange(0, max_dimension - 1);
surface_picker_y_control = new QSpinBox;
surface_picker_y_control->setRange(0, max_dimension - 1);
surface_format_control = new QComboBox;
// Color formats sorted by Maxwell texture format index
surface_format_control->addItem(tr("None"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("A8R8G8B8"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("Unknown"));
surface_format_control->addItem(tr("DXT1"));
surface_format_control->addItem(tr("DXT23"));
surface_format_control->addItem(tr("DXT45"));
surface_format_control->addItem(tr("DXN1"));
surface_format_control->addItem(tr("DXN2"));
surface_info_label = new QLabel();
surface_info_label->setWordWrap(true);
surface_picture_label = new SurfacePicture(0, this);
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
surface_picture_label->setScaledContents(false);
auto scroll_area = new QScrollArea();
scroll_area->setBackgroundRole(QPalette::Dark);
scroll_area->setWidgetResizable(false);
scroll_area->setWidget(surface_picture_label);
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
// Connections
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
SLOT(OnSurfaceSourceChanged(int)));
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
SLOT(OnSurfaceAddressChanged(qint64)));
connect(surface_width_control, SIGNAL(valueChanged(int)), this,
SLOT(OnSurfaceWidthChanged(int)));
connect(surface_height_control, SIGNAL(valueChanged(int)), this,
SLOT(OnSurfaceHeightChanged(int)));
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
SLOT(OnSurfaceFormatChanged(int)));
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
SLOT(OnSurfacePickerXChanged(int)));
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
SLOT(OnSurfacePickerYChanged(int)));
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
auto main_widget = new QWidget;
auto main_layout = new QVBoxLayout;
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Source:")));
sub_layout->addWidget(surface_source_list);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("GPU Address:")));
sub_layout->addWidget(surface_address_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Width:")));
sub_layout->addWidget(surface_width_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Height:")));
sub_layout->addWidget(surface_height_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Format:")));
sub_layout->addWidget(surface_format_control);
main_layout->addLayout(sub_layout);
}
main_layout->addWidget(scroll_area);
auto info_layout = new QHBoxLayout;
{
auto xy_layout = new QVBoxLayout;
{
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("X:")));
sub_layout->addWidget(surface_picker_x_control);
xy_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Y:")));
sub_layout->addWidget(surface_picker_y_control);
xy_layout->addLayout(sub_layout);
}
}
info_layout->addLayout(xy_layout);
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
info_layout->addWidget(surface_info_label);
}
main_layout->addLayout(info_layout);
main_layout->addWidget(save_surface);
main_widget->setLayout(main_layout);
setWidget(main_widget);
// Load current data - TODO: Make sure this works when emulation is not running
if (debug_context && debug_context->at_breakpoint) {
emit Update();
widget()->setEnabled(debug_context->at_breakpoint);
} else {
widget()->setEnabled(false);
}
}
void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
emit Update();
widget()->setEnabled(true);
}
void GraphicsSurfaceWidget::OnResumed() {
widget()->setEnabled(false);
}
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
surface_source = static_cast<Source>(new_value);
emit Update();
}
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
if (surface_address != new_value) {
surface_address = static_cast<Tegra::GPUVAddr>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
if (surface_width != static_cast<unsigned>(new_value)) {
surface_width = static_cast<unsigned>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
if (surface_height != static_cast<unsigned>(new_value)) {
surface_height = static_cast<unsigned>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
if (surface_picker_x != new_value) {
surface_picker_x = new_value;
Pick(surface_picker_x, surface_picker_y);
}
}
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
if (surface_picker_y != new_value) {
surface_picker_y = new_value;
Pick(surface_picker_x, surface_picker_y);
}
}
void GraphicsSurfaceWidget::Pick(int x, int y) {
surface_picker_x_control->setValue(x);
surface_picker_y_control->setValue(y);
if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
y >= static_cast<int>(surface_height)) {
surface_info_label->setText(tr("Pixel out of bounds"));
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
return;
}
surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
}
void GraphicsSurfaceWidget::OnUpdate() {
auto& gpu = Core::System::GetInstance().GPU();
QPixmap pixmap;
switch (surface_source) {
case Source::RenderTarget0:
case Source::RenderTarget1:
case Source::RenderTarget2:
case Source::RenderTarget3:
case Source::RenderTarget4:
case Source::RenderTarget5:
case Source::RenderTarget6:
case Source::RenderTarget7: {
// TODO: Store a reference to the registers in the debug context instead of accessing them
// directly...
auto& registers = gpu.Get3DEngine().regs;
auto& rt = registers.rt[static_cast<size_t>(surface_source) -
static_cast<size_t>(Source::RenderTarget0)];
surface_address = rt.Address();
surface_width = rt.horiz;
surface_height = rt.vert;
if (rt.format != 0) {
surface_format =
ConvertToTextureFormat(static_cast<Tegra::RenderTargetFormat>(rt.format));
}
break;
}
case Source::Custom: {
// Keep user-specified values
break;
}
default:
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
break;
}
surface_address_control->SetValue(surface_address);
surface_width_control->setValue(surface_width);
surface_height_control->setValue(surface_height);
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
if (surface_address == 0) {
surface_picture_label->hide();
surface_info_label->setText(tr("(invalid surface address)"));
surface_info_label->setAlignment(Qt::AlignCenter);
surface_picker_x_control->setEnabled(false);
surface_picker_y_control->setEnabled(false);
save_surface->setEnabled(false);
return;
}
// TODO: Implement a good way to visualize alpha components!
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
auto unswizzled_data =
Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height);
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
surface_width, surface_height);
surface_picture_label->show();
for (unsigned int y = 0; y < surface_height; ++y) {
for (unsigned int x = 0; x < surface_width; ++x) {
Math::Vec4<u8> color;
color[0] = texture_data[x + y * surface_width + 0];
color[1] = texture_data[x + y * surface_width + 1];
color[2] = texture_data[x + y * surface_width + 2];
color[3] = texture_data[x + y * surface_width + 3];
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
}
}
pixmap = QPixmap::fromImage(decoded_image);
surface_picture_label->setPixmap(pixmap);
surface_picture_label->resize(pixmap.size());
// Update the info with pixel data
surface_picker_x_control->setEnabled(true);
surface_picker_y_control->setEnabled(true);
Pick(surface_picker_x, surface_picker_y);
// Enable saving the converted pixmap to file
save_surface->setEnabled(true);
}
void GraphicsSurfaceWidget::SaveSurface() {
QString png_filter = tr("Portable Network Graphic (*.png)");
QString bin_filter = tr("Binary data (*.bin)");
QString selectedFilter;
QString filename = QFileDialog::getSaveFileName(
this, tr("Save Surface"),
QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
if (filename.isEmpty()) {
// If the user canceled the dialog, don't save anything.
return;
}
if (selectedFilter == png_filter) {
const QPixmap* pixmap = surface_picture_label->pixmap();
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
QFile file(filename);
file.open(QIODevice::WriteOnly);
if (pixmap)
pixmap->save(&file, "PNG");
} else if (selectedFilter == bin_filter) {
auto& gpu = Core::System::GetInstance().GPU();
VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
const u8* buffer = Memory::GetPointer(address);
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
QFile file(filename);
file.open(QIODevice::WriteOnly);
int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
QByteArray data(reinterpret_cast<const char*>(buffer), size);
file.write(data);
} else {
UNREACHABLE_MSG("Unhandled filter selected");
}
}

View File

@@ -0,0 +1,97 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QLabel>
#include <QPushButton>
#include "video_core/memory_manager.h"
#include "video_core/textures/texture.h"
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
class QComboBox;
class QSpinBox;
class CSpinBox;
class GraphicsSurfaceWidget;
class SurfacePicture : public QLabel {
Q_OBJECT
public:
explicit SurfacePicture(QWidget* parent = nullptr,
GraphicsSurfaceWidget* surface_widget = nullptr);
~SurfacePicture();
protected slots:
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mousePressEvent(QMouseEvent* event);
private:
GraphicsSurfaceWidget* surface_widget;
};
class GraphicsSurfaceWidget : public BreakPointObserverDock {
Q_OBJECT
using Event = Tegra::DebugContext::Event;
enum class Source {
RenderTarget0 = 0,
RenderTarget1 = 1,
RenderTarget2 = 2,
RenderTarget3 = 3,
RenderTarget4 = 4,
RenderTarget5 = 5,
RenderTarget6 = 6,
RenderTarget7 = 7,
ZBuffer = 8,
Custom = 9,
};
public:
explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
QWidget* parent = nullptr);
void Pick(int x, int y);
public slots:
void OnSurfaceSourceChanged(int new_value);
void OnSurfaceAddressChanged(qint64 new_value);
void OnSurfaceWidthChanged(int new_value);
void OnSurfaceHeightChanged(int new_value);
void OnSurfaceFormatChanged(int new_value);
void OnSurfacePickerXChanged(int new_value);
void OnSurfacePickerYChanged(int new_value);
void OnUpdate();
private slots:
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
void OnResumed() override;
void SaveSurface();
signals:
void Update();
private:
QComboBox* surface_source_list;
CSpinBox* surface_address_control;
QSpinBox* surface_width_control;
QSpinBox* surface_height_control;
QComboBox* surface_format_control;
SurfacePicture* surface_picture_label;
QSpinBox* surface_picker_x_control;
QSpinBox* surface_picker_y_control;
QLabel* surface_info_label;
QPushButton* save_surface;
Source surface_source;
Tegra::GPUVAddr surface_address;
unsigned surface_width;
unsigned surface_height;
Tegra::Texture::TextureFormat surface_format;
int surface_picker_x = 0;
int surface_picker_y = 0;
};

View File

@@ -25,10 +25,13 @@
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
#include "core/settings.h"
#include "video_core/debug_utils/debug_utils.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/registers.h"
#include "yuzu/debugger/wait_tree.h"
@@ -68,6 +71,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
void GMainWindow::ShowCallouts() {}
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
debug_context = Tegra::DebugContext::Construct();
setAcceptDrops(true);
ui.setupUi(this);
statusBar()->hide();
@@ -160,6 +166,16 @@ void GMainWindow::InitializeDebugWidgets() {
connect(this, &GMainWindow::EmulationStopping, registersWidget,
&RegistersWidget::OnEmulationStopping);
graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
graphicsBreakpointsWidget->hide();
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
graphicsSurfaceWidget->hide();
debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
waitTreeWidget = new WaitTreeWidget(this);
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
@@ -324,6 +340,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
Core::System& system{Core::System::GetInstance()};
system.SetGPUDebugContext(debug_context);
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");

View File

@@ -15,17 +15,18 @@ class Config;
class EmuThread;
class GameList;
class GImageInfo;
class GPUCommandStreamWidget;
class GPUCommandListWidget;
class GraphicsBreakPointsWidget;
class GraphicsTracingWidget;
class GraphicsVertexShaderWidget;
class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
class RegistersWidget;
class WaitTreeWidget;
namespace Tegra {
class DebugContext;
}
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -138,6 +139,8 @@ private:
Ui::MainWindow ui;
std::shared_ptr<Tegra::DebugContext> debug_context;
GRenderWindow* render_window;
GameList* game_list;
@@ -158,6 +161,8 @@ private:
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
RegistersWidget* registersWidget;
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
GraphicsSurfaceWidget* graphicsSurfaceWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];

View File

@@ -91,7 +91,7 @@ void Config::ReadValues() {
// Core
Settings::values.cpu_core =
static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 0));
static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 1));
// Renderer
Settings::values.resolution_factor =

View File

@@ -77,7 +77,7 @@ touch_device=
[Core]
# Which CPU core to use for CPU emulation
# 0 (default): Unicorn (slow), 1: Dynarmic (faster)
# 0: Unicorn (slow), 1 (default): Dynarmic (faster)
cpu_core =
[Renderer]
@@ -154,10 +154,6 @@ output_device =
use_virtual_sd =
[System]
# The system model that Citra will try to emulate
# 0: Old 3DS (default), 1: New 3DS
is_new_3ds =
# The system region that Citra will use during emulation
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value =

View File

@@ -6,7 +6,7 @@
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
CITRA_ICON ICON "../../dist/yuzu.ico"
YUZU_ICON ICON "../../dist/yuzu.ico"
/////////////////////////////////////////////////////////////////////////////