diff options
Diffstat (limited to 'src')
59 files changed, 1715 insertions, 359 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 84f9c03a7..9c2e6ed88 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <memory> #include <string> #include "audio_core/audio_core.h" @@ -10,8 +11,8 @@ #include "audio_core/null_sink.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" +#include "common/common_types.h" #include "core/core_timing.h" -#include "core/hle/kernel/vm_manager.h" #include "core/hle/service/dsp_dsp.h" namespace AudioCore { @@ -39,20 +40,8 @@ void Init() { CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); } -void AddAddressSpace(Kernel::VMManager& address_space) { - auto r0_vma = address_space - .MapBackingMemory(DSP::HLE::region0_base, - reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), - sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO) - .MoveFrom(); - address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite); - - auto r1_vma = address_space - .MapBackingMemory(DSP::HLE::region1_base, - reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), - sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO) - .MoveFrom(); - address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite); +std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() { + return DSP::HLE::g_dsp_memory.raw_memory; } void SelectSink(std::string sink_id) { diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 0edf6dd15..ab323ce1f 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -4,11 +4,10 @@ #pragma once +#include <array> #include <string> - -namespace Kernel { -class VMManager; -} +#include "common/common_types.h" +#include "core/memory.h" namespace AudioCore { @@ -17,8 +16,8 @@ constexpr int native_sample_rate = 32728; ///< 32kHz /// Initialise Audio Core void Init(); -/// Add DSP address spaces to a Process. -void AddAddressSpace(Kernel::VMManager& vm_manager); +/// Returns a reference to the array backing DSP memory +std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory(); /// Select the sink to use based on sink id. void SelectSink(std::string sink_id); @@ -29,4 +28,4 @@ void EnableStretching(bool enable); /// Shutdown Audio Core void Shutdown(); -} // namespace +} // namespace AudioCore diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp index 31421fdc6..260b182ed 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -16,31 +16,33 @@ namespace HLE { // Region management -std::array<SharedMemory, 2> g_regions; +DspMemory g_dsp_memory; static size_t CurrentRegionIndex() { // The region with the higher frame counter is chosen unless there is wraparound. // This function only returns a 0 or 1. + u16 frame_counter_0 = g_dsp_memory.region_0.frame_counter; + u16 frame_counter_1 = g_dsp_memory.region_1.frame_counter; - if (g_regions[0].frame_counter == 0xFFFFu && g_regions[1].frame_counter != 0xFFFEu) { + if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) { // Wraparound has occurred. return 1; } - if (g_regions[1].frame_counter == 0xFFFFu && g_regions[0].frame_counter != 0xFFFEu) { + if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) { // Wraparound has occurred. return 0; } - return (g_regions[0].frame_counter > g_regions[1].frame_counter) ? 0 : 1; + return (frame_counter_0 > frame_counter_1) ? 0 : 1; } static SharedMemory& ReadRegion() { - return g_regions[CurrentRegionIndex()]; + return CurrentRegionIndex() == 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; } static SharedMemory& WriteRegion() { - return g_regions[1 - CurrentRegionIndex()]; + return CurrentRegionIndex() != 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; } // Audio processing and mixing diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 0a0f60ac1..94ce48863 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h @@ -31,8 +31,8 @@ namespace HLE { // double-buffer. The frame counter is located as the very last u16 of each region and is // incremented each audio tick. -constexpr VAddr region0_base = 0x1FF50000; -constexpr VAddr region1_base = 0x1FF70000; +constexpr u32 region0_offset = 0x50000; +constexpr u32 region1_offset = 0x70000; /** * The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from @@ -512,7 +512,22 @@ struct SharedMemory { }; ASSERT_DSP_STRUCT(SharedMemory, 0x8000); -extern std::array<SharedMemory, 2> g_regions; +union DspMemory { + std::array<u8, 0x80000> raw_memory; + struct { + u8 unused_0[0x50000]; + SharedMemory region_0; + u8 unused_1[0x18000]; + SharedMemory region_1; + u8 unused_2[0x8000]; + }; +}; +static_assert(offsetof(DspMemory, region_0) == region0_offset, + "DSP region 0 is at the wrong offset"); +static_assert(offsetof(DspMemory, region_1) == region1_offset, + "DSP region 1 is at the wrong offset"); + +extern DspMemory g_dsp_memory; // Structures must have an offset that is a multiple of two. static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 51257520b..a8e3541cd 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QApplication> #include <QFileInfo> #include <QHeaderView> #include <QKeyEvent> @@ -194,6 +195,9 @@ void GameList::onFilterCloseClicked() { } GameList::GameList(GMainWindow* parent) : QWidget{parent} { + watcher = new QFileSystemWatcher(this); + connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); + this->main_window = parent; layout = new QVBoxLayout; tree_view = new QTreeView; @@ -218,7 +222,6 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); - connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. @@ -269,7 +272,22 @@ void GameList::ValidateEntry(const QModelIndex& item) { emit GameChosen(file_path); } -void GameList::DonePopulating() { +void GameList::DonePopulating(QStringList watch_list) { + // Clear out the old directories to watch for changes and add the new ones + auto watch_dirs = watcher->directories(); + if (!watch_dirs.isEmpty()) { + watcher->removePaths(watch_dirs); + } + // Workaround: Add the watch paths in chunks to allow the gui to refresh + // This prevents the UI from stalling when a large number of watch paths are added + // Also artificially caps the watcher to a certain number of directories + constexpr int LIMIT_WATCH_DIRECTORIES = 5000; + constexpr int SLICE_SIZE = 25; + int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); + for (int i = 0; i < len; i += SLICE_SIZE) { + watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); + QCoreApplication::processEvents(); + } tree_view->setEnabled(true); int rowCount = tree_view->model()->rowCount(); search_field->setFilterResult(rowCount, rowCount); @@ -309,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); - auto watch_dirs = watcher.directories(); - if (!watch_dirs.isEmpty()) { - watcher.removePaths(watch_dirs); - } - UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -359,38 +372,6 @@ void GameList::RefreshGameDirectory() { } } -/** - * Adds the game list folder to the QFileSystemWatcher to check for updates. - * - * The file watcher will fire off an update to the game list when a change is detected in the game - * list folder. - * - * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and - * this function is fast enough to not stall the UI thread. If performance is an issue, it should - * be moved to another thread and properly locked to prevent concurrency issues. - * - * @param dir folder to check for changes in - * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each - * directory recursively to the watcher and will update the file list if any of the folders - * change. The number determines how deep the recursion should traverse. - */ -void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { - const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (FileUtil::IsDirectory(physical_name)) { - UpdateWatcherList(physical_name, recursion - 1); - } - return true; - }; - - watcher.addPath(QString::fromStdString(dir)); - if (recursion > 0) { - FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); - } -} - void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -399,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (stop_processing) return false; // Breaks the callback loop. - if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { + bool is_dir = FileUtil::IsDirectory(physical_name); + if (!is_dir && HasSupportedFileExtension(physical_name)) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -416,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), }); - } else if (recursion > 0) { + } else if (is_dir && recursion > 0) { + watch_list.append(QString::fromStdString(physical_name)); AddFstEntriesToGameList(physical_name, recursion - 1); } @@ -428,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign void GameListWorker::run() { stop_processing = false; + watch_list.append(dir_path); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); - emit Finished(); + emit Finished(watch_list); } void GameListWorker::Cancel() { diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index d8f8bc5b6..4823a1296 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -85,10 +85,9 @@ private slots: private: void AddEntry(const QList<QStandardItem*>& entry_items); void ValidateEntry(const QModelIndex& item); - void DonePopulating(); + void DonePopulating(QStringList watch_list); void PopupContextMenu(const QPoint& menu_location); - void UpdateWatcherList(const std::string& path, unsigned int recursion); void RefreshGameDirectory(); bool containsAllWords(QString haystack, QString userinput); @@ -98,5 +97,5 @@ private: QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; - QFileSystemWatcher watcher; + QFileSystemWatcher* watcher = nullptr; }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 3c11b6dd1..d1118ff7f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -170,9 +170,15 @@ signals: * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. */ void EntryReady(QList<QStandardItem*> entry_items); - void Finished(); + + /** + * After the worker has traversed the game directory looking for entries, this signal is emmited + * with a list of folders that should be watched for changes as well. + */ + void Finished(QStringList watch_list); private: + QStringList watch_list; QString dir_path; bool deep_scan; std::atomic_bool stop_processing; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 4b30185f1..6905d2d50 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -38,6 +38,7 @@ set(SRCS param_package.cpp scm_rev.cpp string_util.cpp + telemetry.cpp thread.cpp timer.cpp ) @@ -74,6 +75,7 @@ set(HEADERS string_util.h swap.h synchronized_wrapper.h + telemetry.h thread.h thread_queue_list.h timer.h diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp new file mode 100644 index 000000000..bf1f54886 --- /dev/null +++ b/src/common/telemetry.cpp @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/telemetry.h" + +namespace Telemetry { + +void FieldCollection::Accept(VisitorInterface& visitor) const { + for (const auto& field : fields) { + field.second->Accept(visitor); + } +} + +void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) { + fields[field->GetName()] = std::move(field); +} + +template <class T> +void Field<T>::Accept(VisitorInterface& visitor) const { + visitor.Visit(*this); +} + +template class Field<bool>; +template class Field<double>; +template class Field<float>; +template class Field<u8>; +template class Field<u16>; +template class Field<u32>; +template class Field<u64>; +template class Field<s8>; +template class Field<s16>; +template class Field<s32>; +template class Field<s64>; +template class Field<std::string>; +template class Field<const char*>; +template class Field<std::chrono::microseconds>; + +} // namespace Telemetry diff --git a/src/common/telemetry.h b/src/common/telemetry.h new file mode 100644 index 000000000..dd6bbd759 --- /dev/null +++ b/src/common/telemetry.h @@ -0,0 +1,196 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <map> +#include <memory> +#include <string> +#include "common/common_types.h" + +namespace Telemetry { + +/// Field type, used for grouping fields together in the final submitted telemetry log +enum class FieldType : u8 { + None = 0, ///< No specified field group + App, ///< Citra application fields (e.g. version, branch, etc.) + Session, ///< Emulated session fields (e.g. title ID, log, etc.) + Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.) + UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.) + UserConfig, ///< User configuration fields (e.g. emulated CPU core, renderer, etc.) + UserSystem, ///< User system information (e.g. host CPU type, RAM, etc.) +}; + +struct VisitorInterface; + +/** + * Interface class for telemetry data fields. + */ +class FieldInterface : NonCopyable { +public: + virtual ~FieldInterface() = default; + + /** + * Accept method for the visitor pattern. + * @param visitor Reference to the visitor that will visit this field. + */ + virtual void Accept(VisitorInterface& visitor) const = 0; + + /** + * Gets the name of this field. + * @returns Name of this field as a string. + */ + virtual const std::string& GetName() const = 0; +}; + +/** + * Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our + * telemetry web service. + */ +template <typename T> +class Field : public FieldInterface { +public: + Field(FieldType type, std::string name, const T& value) + : type(type), name(std::move(name)), value(value) {} + + Field(FieldType type, std::string name, T&& value) + : type(type), name(std::move(name)), value(std::move(value)) {} + + Field(const Field& other) : Field(other.type, other.name, other.value) {} + + Field& operator=(const Field& other) { + type = other.type; + name = other.name; + value = other.value; + return *this; + } + + Field& operator=(Field&& other) { + type = other.type; + name = std::move(other.name); + value = std::move(other.value); + return *this; + } + + void Accept(VisitorInterface& visitor) const override; + + const std::string& GetName() const override { + return name; + } + + /** + * Returns the type of the field. + */ + FieldType GetType() const { + return type; + } + + /** + * Returns the value of the field. + */ + const T& GetValue() const { + return value; + } + + inline bool operator==(const Field<T>& other) { + return (type == other.type) && (name == other.name) && (value == other.value); + } + + inline bool operator!=(const Field<T>& other) { + return !(*this == other); + } + +private: + std::string name; ///< Field name, must be unique + FieldType type{}; ///< Field type, used for grouping fields together + T value; ///< Field value +}; + +/** + * Collection of data fields that have been logged. + */ +class FieldCollection final : NonCopyable { +public: + FieldCollection() = default; + + /** + * Accept method for the visitor pattern, visits each field in the collection. + * @param visitor Reference to the visitor that will visit each field. + */ + void Accept(VisitorInterface& visitor) const; + + /** + * Creates a new field and adds it to the field collection. + * @param type Type of the field to add. + * @param name Name of the field to add. + * @param value Value for the field to add. + */ + template <typename T> + void AddField(FieldType type, const char* name, T value) { + return AddField(std::make_unique<Field<T>>(type, name, std::move(value))); + } + + /** + * Adds a new field to the field collection. + * @param field Field to add to the field collection. + */ + void AddField(std::unique_ptr<FieldInterface> field); + +private: + std::map<std::string, std::unique_ptr<FieldInterface>> fields; +}; + +/** + * Telemetry fields visitor interface class. A backend to log to a web service should implement + * this interface. + */ +struct VisitorInterface : NonCopyable { + virtual ~VisitorInterface() = default; + + virtual void Visit(const Field<bool>& field) = 0; + virtual void Visit(const Field<double>& field) = 0; + virtual void Visit(const Field<float>& field) = 0; + virtual void Visit(const Field<u8>& field) = 0; + virtual void Visit(const Field<u16>& field) = 0; + virtual void Visit(const Field<u32>& field) = 0; + virtual void Visit(const Field<u64>& field) = 0; + virtual void Visit(const Field<s8>& field) = 0; + virtual void Visit(const Field<s16>& field) = 0; + virtual void Visit(const Field<s32>& field) = 0; + virtual void Visit(const Field<s64>& field) = 0; + virtual void Visit(const Field<std::string>& field) = 0; + virtual void Visit(const Field<const char*>& field) = 0; + virtual void Visit(const Field<std::chrono::microseconds>& field) = 0; + + /// Completion method, called once all fields have been visited + virtual void Complete() = 0; +}; + +/** + * Empty implementation of VisitorInterface that drops all fields. Used when a functional + * backend implementation is not available. + */ +struct NullVisitor : public VisitorInterface { + ~NullVisitor() = default; + + void Visit(const Field<bool>& /*field*/) override {} + void Visit(const Field<double>& /*field*/) override {} + void Visit(const Field<float>& /*field*/) override {} + void Visit(const Field<u8>& /*field*/) override {} + void Visit(const Field<u16>& /*field*/) override {} + void Visit(const Field<u32>& /*field*/) override {} + void Visit(const Field<u64>& /*field*/) override {} + void Visit(const Field<s8>& /*field*/) override {} + void Visit(const Field<s16>& /*field*/) override {} + void Visit(const Field<s32>& /*field*/) override {} + void Visit(const Field<s64>& /*field*/) override {} + void Visit(const Field<std::string>& /*field*/) override {} + void Visit(const Field<const char*>& /*field*/) override {} + void Visit(const Field<std::chrono::microseconds>& /*field*/) override {} + + void Complete() override {} +}; + +} // namespace Telemetry diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e404063f0..57504529f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -139,6 +139,7 @@ set(SRCS hle/service/nwm/nwm_soc.cpp hle/service/nwm/nwm_tst.cpp hle/service/nwm/nwm_uds.cpp + hle/service/nwm/uds_beacon.cpp hle/service/pm_app.cpp hle/service/ptm/ptm.cpp hle/service/ptm/ptm_gets.cpp @@ -173,6 +174,7 @@ set(SRCS memory.cpp perf_stats.cpp settings.cpp + telemetry_session.cpp ) set(HEADERS @@ -238,6 +240,7 @@ set(HEADERS hle/kernel/semaphore.h hle/kernel/server_port.h hle/kernel/server_session.h + hle/kernel/session.h hle/kernel/shared_memory.h hle/kernel/thread.h hle/kernel/timer.h @@ -326,6 +329,7 @@ set(HEADERS hle/service/nwm/nwm_soc.h hle/service/nwm/nwm_tst.h hle/service/nwm/nwm_uds.h + hle/service/nwm/uds_beacon.h hle/service/pm_app.h hle/service/ptm/ptm.h hle/service/ptm/ptm_gets.h @@ -363,6 +367,7 @@ set(HEADERS mmio.h perf_stats.h settings.h + telemetry_session.h ) include_directories(../../externals/dynarmic/include) diff --git a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp index 7b035f56a..735cf1a09 100644 --- a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp @@ -134,6 +134,19 @@ u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double* vd, #endif if (!(significand & ((1ULL << (VFP_DOUBLE_LOW_BITS + 1)) - 1))) underflow = 0; + + int type = vfp_double_type(vd); + + if ((type & VFP_DENORMAL) && (fpscr & FPSCR_FLUSH_TO_ZERO)) { + // Flush denormal to positive 0 + significand = 0; + + vd->sign = 0; + vd->significand = significand; + + underflow = 0; + exceptions |= FPSCR_UFC; + } } /* diff --git a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp index ae5b325f0..1f9142abc 100644 --- a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp @@ -137,6 +137,19 @@ u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single* vs, #endif if (!(significand & ((1 << (VFP_SINGLE_LOW_BITS + 1)) - 1))) underflow = 0; + + int type = vfp_single_type(vs); + + if ((type & VFP_DENORMAL) && (fpscr & FPSCR_FLUSH_TO_ZERO)) { + // Flush denormal to positive 0 + significand = 0; + + vs->sign = 0; + vs->significand = significand; + + underflow = 0; + exceptions |= FPSCR_UFC; + } } /* @@ -1049,12 +1062,22 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) static u32 vfp_single_fsub(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr) { LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd); /* - * Subtraction is addition with one sign inverted. + * Subtraction is addition with one sign inverted. Unpack the second operand to perform FTZ if + * necessary, we can't let fadd do this because a denormal in m might get flushed to +0 in FTZ + * mode, and the resulting sign of 0 OP +0 differs between fadd and fsub. We do not need to do + * this for n because +0 OP 0 is always +0 for both fadd and fsub. */ + struct vfp_single vsm; + u32 exceptions = vfp_single_unpack(&vsm, m, fpscr); + if (exceptions & FPSCR_IDC) { + // The value was flushed to zero, re-pack it. + m = vfp_single_pack(&vsm); + } + if (m != 0x7FC00000) // Only negate if m isn't NaN. m = vfp_single_packed_negate(m); - return vfp_single_fadd(state, sd, sn, m, fpscr); + return vfp_single_fadd(state, sd, sn, m, fpscr) | exceptions; } /* diff --git a/src/core/core.cpp b/src/core/core.cpp index 140ff6451..450e7566d 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -13,11 +13,11 @@ #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/memory.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/service.h" #include "core/hw/hw.h" #include "core/loader/loader.h" +#include "core/memory_setup.h" #include "core/settings.h" #include "video_core/video_core.h" @@ -123,7 +123,8 @@ void System::Reschedule() { } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { - Memory::Init(); + Memory::InitMemoryMap(); + LOG_DEBUG(HW_Memory, "initialized OK"); if (Settings::values.use_cpu_jit) { cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE); @@ -131,6 +132,8 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); } + telemetry_session = std::make_unique<Core::TelemetrySession>(); + CoreTiming::Init(); HW::Init(); Kernel::Init(system_mode); @@ -161,6 +164,7 @@ void System::Shutdown() { CoreTiming::Shutdown(); cpu_core = nullptr; app_loader = nullptr; + telemetry_session = nullptr; LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index 6c9c936b5..6af772831 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "core/memory.h" #include "core/perf_stats.h" +#include "core/telemetry_session.h" class EmuWindow; class ARM_Interface; @@ -80,6 +81,14 @@ public: return cpu_core != nullptr; } + /** + * Returns a reference to the telemetry session for this emulation session. + * @returns Reference to the telemetry session. + */ + Core::TelemetrySession& TelemetrySession() const { + return *telemetry_session; + } + /// Prepare the core emulation for a reschedule void PrepareReschedule(); @@ -117,6 +126,9 @@ private: /// When true, signals that a reschedule should happen bool reschedule_pending{}; + /// Telemetry session for this emulation session + std::unique_ptr<Core::TelemetrySession> telemetry_session; + static System s_instance; }; @@ -124,4 +136,8 @@ inline ARM_Interface& CPU() { return System::GetInstance().CPU(); } +inline TelemetrySession& Telemetry() { + return System::GetInstance().TelemetrySession(); +} + } // namespace Core diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index 22645f4ec..ddcf4c916 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -19,24 +19,21 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { // AcceptSession before returning from this call. if (active_sessions >= max_sessions) { - // TODO(Subv): Return an error code in this situation after session disconnection is - // implemented. - /*return ResultCode(ErrorDescription::MaxConnectionsReached, - ErrorModule::OS, ErrorSummary::WouldBlock, - ErrorLevel::Temporary);*/ + return ResultCode(ErrorDescription::MaxConnectionsReached, ErrorModule::OS, + ErrorSummary::WouldBlock, ErrorLevel::Temporary); } active_sessions++; // Create a new session pair, let the created sessions inherit the parent port's HLE handler. auto sessions = - ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler); + ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler, this); auto client_session = std::get<SharedPtr<ClientSession>>(sessions); auto server_session = std::get<SharedPtr<ServerSession>>(sessions); if (server_port->hle_handler) server_port->hle_handler->ClientConnected(server_session); - - server_port->pending_sessions.push_back(std::move(server_session)); + else + server_port->pending_sessions.push_back(std::move(server_session)); // Wake the threads waiting on the ServerPort server_port->WakeupAllWaitingThreads(); diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 0331386ec..e297b7464 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -14,27 +14,24 @@ ClientSession::~ClientSession() { // This destructor will be called automatically when the last ClientSession handle is closed by // the emulated application. - if (server_session->hle_handler) - server_session->hle_handler->ClientDisconnected(server_session); + if (parent->server) { + if (parent->server->hle_handler) + parent->server->hle_handler->ClientDisconnected(parent->server); - // TODO(Subv): If the session is still open, set the connection status to 2 (Closed by client), - // wake up all the ServerSession's waiting threads and set the WaitSynchronization result to - // 0xC920181A. -} - -ResultVal<SharedPtr<ClientSession>> ClientSession::Create(ServerSession* server_session, - std::string name) { - SharedPtr<ClientSession> client_session(new ClientSession); + // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set + // their WaitSynchronization result to 0xC920181A. + } - client_session->name = std::move(name); - client_session->server_session = server_session; - client_session->session_status = SessionStatus::Open; - return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); + parent->client = nullptr; } ResultCode ClientSession::SendSyncRequest() { // Signal the server session that new data is available - return server_session->HandleSyncRequest(); + if (parent->server) + return parent->server->HandleSyncRequest(); + + return ResultCode(ErrorDescription::SessionClosedByRemote, ErrorModule::OS, + ErrorSummary::Canceled, ErrorLevel::Status); } } // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index ed468dec6..9f3adb72b 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -14,12 +14,7 @@ namespace Kernel { class ServerSession; - -enum class SessionStatus { - Open = 1, - ClosedByClient = 2, - ClosedBYServer = 3, -}; +class Session; class ClientSession final : public Object { public: @@ -44,22 +39,14 @@ public: */ ResultCode SendSyncRequest(); - std::string name; ///< Name of client port (optional) - ServerSession* server_session; ///< The server session associated with this client session. - SessionStatus session_status; ///< The session's current status. + std::string name; ///< Name of client port (optional) + + /// The parent session, which links to the server endpoint. + std::shared_ptr<Session> parent; private: ClientSession(); ~ClientSession() override; - - /** - * Creates a client session. - * @param server_session The server session associated with this client session - * @param name Optional name of client session - * @return The created client session - */ - static ResultVal<SharedPtr<ClientSession>> Create(ServerSession* server_session, - std::string name = "Unknown"); }; } // namespace diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 33c165197..8250a90b5 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -2,11 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include <map> #include <memory> #include <utility> #include <vector> #include "audio_core/audio_core.h" +#include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -92,52 +94,96 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) { UNREACHABLE(); } } -} - -namespace Memory { -namespace { +std::array<u8, Memory::VRAM_SIZE> vram; +std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; + +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { + using namespace Memory; + + struct MemoryArea { + VAddr vaddr_base; + PAddr paddr_base; + u32 size; + }; + + // The order of entries in this array is important. The VRAM and IO VAddr ranges overlap, and + // VRAM must be tried first. + static constexpr MemoryArea memory_areas[] = { + {VRAM_VADDR, VRAM_PADDR, VRAM_SIZE}, + {IO_AREA_VADDR, IO_AREA_PADDR, IO_AREA_SIZE}, + {DSP_RAM_VADDR, DSP_RAM_PADDR, DSP_RAM_SIZE}, + {N3DS_EXTRA_RAM_VADDR, N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE - 0x20000}, + }; + + VAddr mapping_limit = mapping.address + mapping.size; + if (mapping_limit < mapping.address) { + LOG_CRITICAL(Loader, "Mapping size overflowed: address=0x%08" PRIX32 " size=0x%" PRIX32, + mapping.address, mapping.size); + return; + } -struct MemoryArea { - u32 base; - u32 size; - const char* name; -}; + auto area = + std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) { + return mapping.address >= area.vaddr_base && + mapping_limit <= area.vaddr_base + area.size; + }); + if (area == std::end(memory_areas)) { + LOG_ERROR(Loader, "Unhandled special mapping: address=0x%08" PRIX32 " size=0x%" PRIX32 + " read_only=%d unk_flag=%d", + mapping.address, mapping.size, mapping.read_only, mapping.unk_flag); + return; + } -// We don't declare the IO regions in here since its handled by other means. -static MemoryArea memory_areas[] = { - {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) -}; -} + u32 offset_into_region = mapping.address - area->vaddr_base; + if (area->paddr_base == IO_AREA_PADDR) { + LOG_ERROR(Loader, "MMIO mappings are not supported yet. phys_addr=0x%08" PRIX32, + area->paddr_base + offset_into_region); + return; + } -void Init() { - InitMemoryMap(); - LOG_DEBUG(HW_Memory, "initialized OK"); -} + // TODO(yuriks): Use GetPhysicalPointer when that becomes independent of the virtual + // mappings. + u8* target_pointer = nullptr; + switch (area->paddr_base) { + case VRAM_PADDR: + target_pointer = vram.data(); + break; + case DSP_RAM_PADDR: + target_pointer = AudioCore::GetDspMemory().data(); + break; + case N3DS_EXTRA_RAM_PADDR: + target_pointer = n3ds_extra_ram.data(); + break; + default: + UNREACHABLE(); + } -void InitLegacyAddressSpace(Kernel::VMManager& address_space) { - using namespace Kernel; + // TODO(yuriks): This flag seems to have some other effect, but it's unknown what + MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO; - for (MemoryArea& area : memory_areas) { - auto block = std::make_shared<std::vector<u8>>(area.size); - address_space - .MapMemoryBlock(area.base, std::move(block), 0, area.size, MemoryState::Private) - .Unwrap(); - } + auto vma = address_space + .MapBackingMemory(mapping.address, target_pointer + offset_into_region, + mapping.size, memory_state) + .MoveFrom(); + address_space.Reprotect(vma, + mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); +} +void MapSharedPages(VMManager& address_space) { auto cfg_mem_vma = address_space - .MapBackingMemory(CONFIG_MEMORY_VADDR, (u8*)&ConfigMem::config_mem, - CONFIG_MEMORY_SIZE, MemoryState::Shared) + .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, + reinterpret_cast<u8*>(&ConfigMem::config_mem), + Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared) .MoveFrom(); address_space.Reprotect(cfg_mem_vma, VMAPermission::Read); auto shared_page_vma = address_space - .MapBackingMemory(SHARED_PAGE_VADDR, (u8*)&SharedPage::shared_page, - SHARED_PAGE_SIZE, MemoryState::Shared) + .MapBackingMemory(Memory::SHARED_PAGE_VADDR, + reinterpret_cast<u8*>(&SharedPage::shared_page), + Memory::SHARED_PAGE_SIZE, MemoryState::Shared) .MoveFrom(); address_space.Reprotect(shared_page_vma, VMAPermission::Read); - - AudioCore::AddAddressSpace(address_space); } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index 4e1856a41..08c1a9989 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h @@ -23,11 +23,7 @@ struct MemoryRegionInfo { void MemoryInit(u32 mem_type); void MemoryShutdown(); MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); -} -namespace Memory { - -void Init(); -void InitLegacyAddressSpace(Kernel::VMManager& address_space); - -} // namespace +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); +void MapSharedPages(VMManager& address_space); +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ba80fe7f8..32cb25fb7 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -35,7 +35,6 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) { process->codeset = std::move(code_set); process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); - Memory::InitLegacyAddressSpace(process->vm_manager); return process; } @@ -78,8 +77,15 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { AddressMapping mapping; mapping.address = descriptor << 12; - mapping.size = (end_desc << 12) - mapping.address; - mapping.writable = (descriptor & (1 << 20)) != 0; + VAddr end_address = end_desc << 12; + + if (mapping.address < end_address) { + mapping.size = end_address - mapping.address; + } else { + mapping.size = 0; + } + + mapping.read_only = (descriptor & (1 << 20)) != 0; mapping.unk_flag = (end_desc & (1 << 20)) != 0; address_mappings.push_back(mapping); @@ -88,8 +94,10 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { AddressMapping mapping; mapping.address = descriptor << 12; mapping.size = Memory::PAGE_SIZE; - mapping.writable = true; // TODO: Not sure if correct + mapping.read_only = false; mapping.unk_flag = false; + + address_mappings.push_back(mapping); } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF // Kernel version kernel_version = descriptor & 0xFFFF; @@ -131,6 +139,12 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { misc_memory_used += stack_size; memory_region->used += stack_size; + // Map special address mappings + MapSharedPages(vm_manager); + for (const auto& mapping : address_mappings) { + HandleSpecialMapping(vm_manager, mapping); + } + vm_manager.LogLayout(Log::Level::Debug); Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority); } @@ -138,6 +152,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { VAddr Process::GetLinearHeapAreaAddress() const { return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR; } + VAddr Process::GetLinearHeapBase() const { return GetLinearHeapAreaAddress() + memory_region->base; } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index b566950b0..b52211d2a 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -20,7 +20,7 @@ struct AddressMapping { // Address and size must be page-aligned VAddr address; u32 size; - bool writable; + bool read_only; bool unk_flag; }; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 9447ff236..500b909ab 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -14,8 +14,15 @@ ServerSession::ServerSession() = default; ServerSession::~ServerSession() { // This destructor will be called automatically when the last ServerSession handle is closed by // the emulated application. - // TODO(Subv): Reduce the ClientPort's connection count, - // if the session is still open, set the connection status to 3 (Closed by server), + + // Decrease the port's connection count. + if (parent->port) + parent->port->active_sessions--; + + // TODO(Subv): Wake up all the ClientSession's waiting threads and set + // the SendSyncRequest result to 0xC920181A. + + parent->server = nullptr; } ResultVal<SharedPtr<ServerSession>> ServerSession::Create( @@ -25,6 +32,7 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( server_session->name = std::move(name); server_session->signaled = false; server_session->hle_handler = std::move(hle_handler); + server_session->parent = nullptr; return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } @@ -61,13 +69,22 @@ ResultCode ServerSession::HandleSyncRequest() { } ServerSession::SessionPair ServerSession::CreateSessionPair( - const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler, + SharedPtr<ClientPort> port) { + auto server_session = ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); - // We keep a non-owning pointer to the ServerSession in the ClientSession because we don't want - // to prevent the ServerSession's destructor from being called when the emulated - // application closes the last ServerSession handle. - auto client_session = ClientSession::Create(server_session.get(), name + "_Client").MoveFrom(); + + SharedPtr<ClientSession> client_session(new ClientSession); + client_session->name = name + "_Client"; + + std::shared_ptr<Session> parent(new Session); + parent->client = client_session.get(); + parent->server = server_session.get(); + parent->port = port; + + client_session->parent = parent; + server_session->parent = parent; return std::make_tuple(std::move(server_session), std::move(client_session)); } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 761fc4781..c907d487c 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -17,6 +18,8 @@ namespace Kernel { class ClientSession; +class ClientPort; +class ServerSession; /** * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS @@ -47,11 +50,13 @@ public: * Creates a pair of ServerSession and an associated ClientSession. * @param name Optional name of the ports. * @param hle_handler Optional HLE handler for this server session. + * @param client_port Optional The ClientPort that spawned this session. * @return The created session tuple */ static SessionPair CreateSessionPair( const std::string& name = "Unknown", - std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr, + SharedPtr<ClientPort> client_port = nullptr); /** * Handle a sync request from the emulated application. @@ -63,8 +68,9 @@ public: void Acquire(Thread* thread) override; - std::string name; ///< The name of this session (optional) - bool signaled; ///< Whether there's new data available to this ServerSession + std::string name; ///< The name of this session (optional) + bool signaled; ///< Whether there's new data available to this ServerSession + std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint. std::shared_ptr<Service::SessionRequestHandler> hle_handler; ///< This session's HLE request handler (optional) diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h new file mode 100644 index 000000000..a45e78022 --- /dev/null +++ b/src/core/hle/kernel/session.h @@ -0,0 +1,27 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class ClientSession; +class ClientPort; +class ServerSession; + +/** + * Parent structure to link the client and server endpoints of a session with their associated + * client port. The client port need not exist, as is the case for portless sessions like the + * FS File and Directory sessions. When one of the endpoints of a session is destroyed, its + * corresponding field in this structure will be set to nullptr. + */ +class Session final { +public: + ClientSession* client = nullptr; ///< The client endpoint of the session. + ServerSession* server = nullptr; ///< The server endpoint of the session. + SharedPtr<ClientPort> port; ///< The port that this session is associated with (optional). +}; +} diff --git a/src/core/hle/result.h b/src/core/hle/result.h index cfefbbc64..13b948871 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -16,6 +16,7 @@ /// Detailed description of the error. This listing is likely incomplete. enum class ErrorDescription : u32 { Success = 0, + SessionClosedByRemote = 26, WrongPermission = 46, OS_InvalidBufferDescriptor = 48, MaxConnectionsReached = 52, diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b19e831fe..64d01cdd7 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -53,30 +53,29 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton:: buttons; static std::unique_ptr<Input::AnalogDevice> circle_pad; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; - PadState state; - state.hex = 0; + DirectionState state{false, false, false, false}; if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x); if (circle_pad_x != 0 && t < TAN60) { if (circle_pad_x > 0) - state.circle_right.Assign(1); + state.right = true; else - state.circle_left.Assign(1); + state.left = true; } if (circle_pad_x == 0 || t > TAN30) { if (circle_pad_y > 0) - state.circle_up.Assign(1); + state.up = true; else - state.circle_down.Assign(1); + state.down = true; } } @@ -125,7 +124,11 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); - state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b505cdcd5..1ef972e70 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -176,6 +176,16 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) +struct DirectionState { + bool up; + bool down; + bool left; + bool right; +}; + +/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); + /** * HID::GetIPCHandles service function * Inputs: diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7ac34a990..f06dd552f 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -25,6 +25,11 @@ void Shutdown() { ShutdownRST(); } +void ReloadInputDevices() { + ReloadInputDevicesUser(); + ReloadInputDevicesRST(); +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index c741498e2..6be3e950c 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -16,5 +16,8 @@ void Init(); /// Shutdown IR service void Shutdown(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 3f1275c53..53807cd91 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,16 +2,135 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> +#include "common/bit_field.h" +#include "core/core_timing.h" +#include "core/frontend/input.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" +#include "core/settings.h" namespace Service { namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; +union PadState { + u32_le hex; + + BitField<14, 1, u32_le> zl; + BitField<15, 1, u32_le> zr; + + BitField<24, 1, u32_le> c_stick_right; + BitField<25, 1, u32_le> c_stick_left; + BitField<26, 1, u32_le> c_stick_up; + BitField<27, 1, u32_le> c_stick_down; +}; + +struct PadDataEntry { + PadState current_state; + PadState delta_additions; + PadState delta_removals; + + s16_le c_stick_x; + s16_le c_stick_y; +}; + +struct SharedMem { + u64_le index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + u64_le index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32_le index; + INSERT_PADDING_WORDS(1); + std::array<PadDataEntry, 8> entries; ///< Last 8 pad entries +}; + +static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); + +static Kernel::SharedPtr<Kernel::Event> update_event; static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static u32 next_pad_index; +static int update_callback_id; +static std::unique_ptr<Input::ButtonDevice> zl_button; +static std::unique_ptr<Input::ButtonDevice> zr_button; +static std::unique_ptr<Input::AnalogDevice> c_stick; +static std::atomic<bool> is_device_reload_pending; +static bool raw_c_stick; +static int update_period; + +static void LoadInputDevices() { + zl_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +static void UnloadInputDevices() { + zl_button = nullptr; + zr_button = nullptr; + c_stick = nullptr; +} + +static void UpdateCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_memory->GetPointer()); + + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); + const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } + + // TODO (wwylele): implement raw C-stick data for raw_c_stick = true + + const u32 last_entry_index = mem->index; + mem->index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->entries.size(); + + // Get the previous Pad state + PadState old_state{mem->entries[last_entry_index].current_state}; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {state.hex ^ old_state.hex}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->entries[mem->index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.c_stick_x = c_stick_x; + pad_entry.c_stick_y = c_stick_y; + + // If we just updated index 0, provide a new timestamp + if (mem->index == 0) { + mem->index_reset_ticks_previous = mem->index_reset_ticks; + mem->index_reset_ticks = CoreTiming::GetTicks(); + } + + update_event->Signal(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback_id); +} /** * IR::GetHandles service function @@ -22,18 +141,52 @@ static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; * 4 : Event handle */ static void GetHandles(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(), + Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom()); +} + +/** + * IR::Initialize service function + * Inputs: + * 1 : pad state update period in ms + * 2 : bool output raw c-stick data + */ +static void Initialize(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 0); + update_period = static_cast<int>(rp.Pop<u32>()); + raw_c_stick = rp.Pop<bool>(); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x4000000; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); + if (raw_c_stick) + LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); + + next_pad_index = 0; + is_device_reload_pending.store(true); + CoreTiming::ScheduleEvent(msToCycles(update_period), update_callback_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_IR, "called. update_period=%d, raw_c_stick=%d", update_period, raw_c_stick); +} + +static void Shutdown(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + + CoreTiming::UnscheduleEvent(update_callback_id, 0); + UnloadInputDevices(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_IR, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, - {0x00020080, nullptr, "Initialize"}, - {0x00030000, nullptr, "Shutdown"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030000, Shutdown, "Shutdown"}, {0x00090000, nullptr, "WriteToTwoFields"}, }; @@ -43,17 +196,24 @@ IR_RST_Interface::IR_RST_Interface() { void InitRST() { using namespace Kernel; - + // Note: these two kernel objects are even available before Initialize service function is + // called. shared_memory = - SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, MemoryRegion::BASE, "IRRST:SharedMemory"); + update_event = Event::Create(ResetType::OneShot, "IRRST:UpdateEvent"); - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); + update_callback_id = CoreTiming::RegisterEvent("IRRST:UpdateCallBack", UpdateCallback); } void ShutdownRST() { shared_memory = nullptr; - handle_event = nullptr; + update_event = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevicesRST() { + is_device_reload_pending.store(true); } } // namespace IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 75b732627..d932bb7e5 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,5 +21,8 @@ public: void InitRST(); void ShutdownRST(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index bccf6bce7..226af0083 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -542,7 +542,7 @@ void ShutdownUser() { receive_event = nullptr; } -void ReloadInputDevices() { +void ReloadInputDevicesUser() { if (extra_hid) extra_hid->RequestInputDevicesReload(); } diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 2401346e8..930650406 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -52,7 +52,7 @@ void InitUser(); void ShutdownUser(); /// Reload input devices. Used when input configuration changed -void ReloadInputDevices(); +void ReloadInputDevicesUser(); } // namespace IR } // namespace Service diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index ef6c5ebe3..581816e81 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <cstring> #include <unordered_map> #include <vector> @@ -12,6 +13,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" #include "core/memory.h" namespace Service { @@ -27,10 +29,12 @@ static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory; // Connection status of this 3DS. static ConnectionStatus connection_status{}; -// Node information about the current 3DS. -// TODO(Subv): Keep an array of all nodes connected to the network, -// that data has to be retransmitted in every beacon frame. -static NodeInfo node_info; +/* Node information about the current network. + * The amount of elements in this vector is always the maximum number + * of nodes specified in the network configuration. + * The first node is always the host, so this always contains at least 1 entry. + */ +static NodeList node_info(1); // Mapping of bind node ids to their respective events. static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; @@ -82,29 +86,70 @@ static void Shutdown(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void RecvBeaconBroadcastData(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 out_buffer_size = cmd_buff[1]; - u32 unk1 = cmd_buff[2]; - u32 unk2 = cmd_buff[3]; - u32 mac_address = cmd_buff[4]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 16, 4); - u32 unk3 = cmd_buff[6]; + u32 out_buffer_size = rp.Pop<u32>(); + u32 unk1 = rp.Pop<u32>(); + u32 unk2 = rp.Pop<u32>(); - u32 wlan_comm_id = cmd_buff[15]; - u32 ctr_gen_id = cmd_buff[16]; - u32 value = cmd_buff[17]; - u32 input_handle = cmd_buff[18]; - u32 new_buffer_size = cmd_buff[19]; - u32 out_buffer_ptr = cmd_buff[20]; + MacAddress mac_address; + rp.PopRaw(mac_address); - cmd_buff[1] = RESULT_SUCCESS.raw; + rp.Skip(9, false); - LOG_WARNING(Service_NWM, - "(STUBBED) called out_buffer_size=0x%08X, unk1=0x%08X, unk2=0x%08X," - "mac_address=0x%08X, unk3=0x%08X, wlan_comm_id=0x%08X, ctr_gen_id=0x%08X," - "value=%u, input_handle=0x%08X, new_buffer_size=0x%08X, out_buffer_ptr=0x%08X", - out_buffer_size, unk1, unk2, mac_address, unk3, wlan_comm_id, ctr_gen_id, value, - input_handle, new_buffer_size, out_buffer_ptr); + u32 wlan_comm_id = rp.Pop<u32>(); + u32 id = rp.Pop<u32>(); + Kernel::Handle input_handle = rp.PopHandle(); + + size_t desc_size; + const VAddr out_buffer_ptr = rp.PopMappedBuffer(&desc_size); + ASSERT(desc_size == out_buffer_size); + + VAddr current_buffer_pos = out_buffer_ptr; + u32 total_size = sizeof(BeaconDataReplyHeader); + + // Retrieve all beacon frames that were received from the desired mac address. + std::deque<WifiPacket> beacons = + GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address); + + BeaconDataReplyHeader data_reply_header{}; + data_reply_header.total_entries = beacons.size(); + data_reply_header.max_output_size = out_buffer_size; + + Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); + current_buffer_pos += sizeof(BeaconDataReplyHeader); + + // Write each of the received beacons into the buffer + for (const auto& beacon : beacons) { + BeaconEntryHeader entry{}; + // TODO(Subv): Figure out what this size is used for. + entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size(); + entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size(); + entry.wifi_channel = beacon.channel; + entry.header_size = sizeof(BeaconEntryHeader); + entry.mac_address = beacon.transmitter_address; + + ASSERT(current_buffer_pos < out_buffer_ptr + out_buffer_size); + + Memory::WriteBlock(current_buffer_pos, &entry, sizeof(BeaconEntryHeader)); + current_buffer_pos += sizeof(BeaconEntryHeader); + + Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); + current_buffer_pos += beacon.data.size(); + + total_size += sizeof(BeaconEntryHeader) + beacon.data.size(); + } + + // Update the total size in the structure and write it to the buffer again. + data_reply_header.total_size = total_size; + Memory::WriteBlock(out_buffer_ptr, &data_reply_header, sizeof(BeaconDataReplyHeader)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_NWM, "called out_buffer_size=0x%08X, wlan_comm_id=0x%08X, id=0x%08X," + "input_handle=0x%08X, out_buffer_ptr=0x%08X, unk1=0x%08X, unk2=0x%08X", + out_buffer_size, wlan_comm_id, id, input_handle, out_buffer_ptr, unk1, unk2); } /** @@ -127,10 +172,10 @@ static void InitializeWithVersion(Interface* self) { u32 sharedmem_size = rp.Pop<u32>(); // Update the node information with the data the game gave us. - rp.PopRaw(node_info); + rp.PopRaw(node_info[0]); + + u16 version = rp.Pop<u16>(); - u16 version; - rp.PopRaw(version); Kernel::Handle sharedmem_handle = rp.PopHandle(); recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); @@ -191,10 +236,8 @@ static void Bind(Interface* self) { u32 bind_node_id = rp.Pop<u32>(); u32 recv_buffer_size = rp.Pop<u32>(); - u8 data_channel; - rp.PopRaw(data_channel); - u16 network_node_id; - rp.PopRaw(network_node_id); + u8 data_channel = rp.Pop<u8>(); + u16 network_node_id = rp.Pop<u16>(); // TODO(Subv): Store the data channel and verify it when receiving data frames. @@ -251,13 +294,25 @@ static void BeginHostingNetwork(Interface* self) { ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); + + // Ensure the application data size is less than the maximum value. + ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + + // Set up basic information for this network. + network_info.oui_value = NintendoOUI; + network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo); + connection_status.max_nodes = network_info.max_nodes; + // Resize the nodes list to hold max_nodes. + node_info.resize(network_info.max_nodes); + // There's currently only one node in the network (the host). connection_status.total_nodes = 1; + network_info.total_nodes = 1; // The host is always the first node connection_status.network_node_id = 1; - node_info.network_node_id = 1; + node_info[0].network_node_id = 1; // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. connection_status.node_bitmask |= 1; @@ -325,7 +380,7 @@ static void GetChannel(Interface* self) { u8 channel = is_connected ? network_channel : 0; rb.Push(RESULT_SUCCESS); - rb.PushRaw(channel); + rb.Push(channel); LOG_DEBUG(Service_NWM, "called"); } @@ -373,7 +428,8 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) return; - // TODO(Subv): Actually generate the beacon and send it. + // TODO(Subv): Actually send the beacon. + std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); // Start broadcasting the network, send a beacon frame every 102.4ms. CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 65349f9fd..29b146569 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -6,6 +6,7 @@ #include <array> #include <cstddef> +#include <vector> #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/service.h" @@ -33,6 +34,8 @@ struct NodeInfo { static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); +using NodeList = std::vector<NodeInfo>; + enum class NetworkStatus { NotConnected = 3, ConnectedAsHost = 6, @@ -75,6 +78,8 @@ struct NetworkInfo { std::array<u8, ApplicationDataSize> application_data; }; +static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wrong offset."); +static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); class NWM_UDS final : public Interface { diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp new file mode 100644 index 000000000..c6e5bc5f1 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.cpp @@ -0,0 +1,333 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" + +#include <cryptopp/aes.h> +#include <cryptopp/md5.h> +#include <cryptopp/modes.h> +#include <cryptopp/sha.h> + +namespace Service { +namespace NWM { + +// 802.11 broadcast MAC address +constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +constexpr u64 DefaultNetworkUptime = 900000000; // 15 minutes in microseconds. + +// Note: These values were taken from a packet capture of an o3DS XL +// broadcasting a Super Smash Bros. 4 lobby. +constexpr u16 DefaultExtraCapabilities = 0x0431; + +// Size of the SSID broadcast by an UDS beacon frame. +constexpr u8 UDSBeaconSSIDSize = 8; + +// The maximum size of the data stored in the EncryptedData0 tag (24). +constexpr u32 EncryptedDataSizeCutoff = 0xFA; + +/** + * NWM Beacon data encryption key, taken from the NWM module code. + * We stub this with an all-zeros key as that is enough for Citra's purpose. + * The real key can be used here to generate beacons that will be accepted by + * a real 3ds. + */ +constexpr std::array<u8, CryptoPP::AES::BLOCKSIZE> nwm_beacon_key = {}; + +/** + * Generates a buffer with the fixed parameters of an 802.11 Beacon frame + * using dummy values. + * @returns A buffer with the fixed parameters of the beacon frame. + */ +std::vector<u8> GenerateFixedParameters() { + std::vector<u8> buffer(sizeof(BeaconFrameHeader)); + + BeaconFrameHeader header{}; + // Use a fixed default time for now. + // TODO(Subv): Perhaps use the difference between now and the time the network was started? + header.timestamp = DefaultNetworkUptime; + header.beacon_interval = DefaultBeaconInterval; + header.capabilities = DefaultExtraCapabilities; + + std::memcpy(buffer.data(), &header, sizeof(header)); + + return buffer; +} + +/** + * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte all-zero SSID value. + * @returns A buffer with the SSID tag. + */ +std::vector<u8> GenerateSSIDTag() { + std::vector<u8> buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); + + TagHeader tag_header{}; + tag_header.tag_id = static_cast<u8>(TagId::SSID); + tag_header.length = UDSBeaconSSIDSize; + + std::memcpy(buffer.data(), &tag_header, sizeof(TagHeader)); + + // The rest of the buffer is already filled with zeros. + + return buffer; +} + +/** + * Generates a buffer with the basic tagged parameters of an 802.11 Beacon frame + * such as SSID, Rate Information, Country Information, etc. + * @returns A buffer with the tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateBasicTaggedParameters() { + // Append the SSID tag + std::vector<u8> buffer = GenerateSSIDTag(); + + // TODO(Subv): Add the SupportedRates tag. + // TODO(Subv): Add the DSParameterSet tag. + // TODO(Subv): Add the TrafficIndicationMap tag. + // TODO(Subv): Add the CountryInformation tag. + // TODO(Subv): Add the ERPInformation tag. + + return buffer; +} + +/** + * Generates a buffer with the Dummy Nintendo tag. + * It is currently unknown what this tag does. + * TODO(Subv): Figure out if this is needed and what it does. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoDummyTag() { + // Note: These values were taken from a packet capture of an o3DS XL + // broadcasting a Super Smash Bros. 4 lobby. + constexpr std::array<u8, 3> dummy_data = {0x0A, 0x00, 0x00}; + + DummyTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); + tag.oui_type = static_cast<u8>(NintendoTagId::Dummy); + tag.oui = NintendoOUI; + tag.data = dummy_data; + + std::vector<u8> buffer(sizeof(DummyTag)); + std::memcpy(buffer.data(), &tag, sizeof(DummyTag)); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the network information of the network that is being broadcast. + * It also contains the application data provided by the application that opened the network. + * @returns A buffer with the Nintendo network info parameter of the beacon frame. + */ +std::vector<u8> GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { + NetworkInfoTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = + sizeof(NetworkInfoTag) - sizeof(TagHeader) + network_info.application_data_size; + tag.appdata_size = network_info.application_data_size; + // Set the hash to zero initially, it will be updated once we calculate it. + tag.sha_hash = {}; + + // Ensure the network structure has the correct OUI and OUI type. + ASSERT(network_info.oui_type == static_cast<u8>(NintendoTagId::NetworkInfo)); + ASSERT(network_info.oui_value == NintendoOUI); + + // Ensure the application data size is less than the maximum value. + ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + + // This tag contains the network info structure starting at the OUI. + std::memcpy(tag.network_info.data(), &network_info.oui_value, tag.network_info.size()); + + // Copy the tag and the data so we can calculate the SHA1 over it. + std::vector<u8> buffer(sizeof(tag) + network_info.application_data_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + std::memcpy(buffer.data() + sizeof(tag), network_info.application_data.data(), + network_info.application_data_size); + + // Calculate the SHA1 of the contents of the tag. + std::array<u8, CryptoPP::SHA1::DIGESTSIZE> hash; + CryptoPP::SHA1().CalculateDigest(hash.data(), + buffer.data() + offsetof(NetworkInfoTag, network_info), + buffer.size() - sizeof(TagHeader)); + + // Copy it directly into the buffer, overwriting the zeros that we had previously placed there. + std::memcpy(buffer.data() + offsetof(NetworkInfoTag, sha_hash), hash.data(), hash.size()); + + return buffer; +} + +/* + * Calculates the CTR used for the AES-CTR encryption of the data stored in the + * EncryptedDataTags. + * @returns The CTR used for beacon crypto. + */ +std::array<u8, CryptoPP::AES::BLOCKSIZE> GetBeaconCryptoCTR(const NetworkInfo& network_info) { + BeaconDataCryptoCTR data{}; + + data.host_mac = network_info.host_mac_address; + data.wlan_comm_id = network_info.wlan_comm_id; + data.id = network_info.id; + data.network_id = network_info.network_id; + + std::array<u8, CryptoPP::AES::BLOCKSIZE> hash; + std::memcpy(hash.data(), &data, sizeof(data)); + + return hash; +} + +/* + * Serializes the node information into the format needed for network transfer, + * and then encrypts it with the NWM key. + * @returns The serialized and encrypted node information. + */ +std::vector<u8> GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector<u8> buffer(sizeof(BeaconData)); + + BeaconData data{}; + std::memcpy(buffer.data(), &data, sizeof(BeaconData)); + + for (const NodeInfo& node : nodes) { + // Serialize each node and convert the data from + // host byte-order to Big Endian. + BeaconNodeInfo info{}; + info.friend_code_seed = node.friend_code_seed; + info.network_node_id = node.network_node_id; + for (int i = 0; i < info.username.size(); ++i) + info.username[i] = node.username[i]; + + buffer.insert(buffer.end(), reinterpret_cast<u8*>(&info), + reinterpret_cast<u8*>(&info) + sizeof(info)); + } + + // Calculate the MD5 hash of the data in the buffer, not including the hash field. + std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash; + CryptoPP::MD5().CalculateDigest(hash.data(), buffer.data() + offsetof(BeaconData, bitmask), + buffer.size() - sizeof(data.md5_hash)); + + // Copy the hash into the buffer. + std::memcpy(buffer.data(), hash.data(), hash.size()); + + // Encrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode<AES>::Encryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); + + return buffer; +} + +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer) { + // Decrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode<AES>::Decryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the first portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * @returns A buffer with the first Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + const size_t payload_size = + std::min<size_t>(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; + tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); + tag.oui = NintendoOUI; + + std::vector<u8> buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data(), payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the second portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * This tag is only present if the payload size is greater than EncryptedDataSizeCutoff (0xFA) + * bytes. + * @returns A buffer with the second Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + // This tag is only present if the payload is larger than EncryptedDataSizeCutoff (0xFA). + if (nodes.size() * sizeof(NodeInfo) <= EncryptedDataSizeCutoff) + return {}; + + const size_t payload_size = nodes.size() * sizeof(NodeInfo) - EncryptedDataSizeCutoff; + + const size_t tag_length = sizeof(EncryptedDataTag) - sizeof(TagHeader) + payload_size; + + // TODO(Subv): What does the 3DS do when a game has too much data to fit into the tag? + ASSERT_MSG(tag_length <= 255, "Data is too big."); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = tag_length; + tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); + tag.oui = NintendoOUI; + + std::vector<u8> buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data() + EncryptedDataSizeCutoff, + payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Nintendo tagged parameters of an 802.11 Beacon frame + * for UDS communication. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoTaggedParameters(const NetworkInfo& network_info, + const NodeList& nodes) { + ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); + + std::vector<u8> buffer = GenerateNintendoDummyTag(); + std::vector<u8> network_info_tag = GenerateNintendoNetworkInfoTag(network_info); + std::vector<u8> first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); + std::vector<u8> second_data_tag = GenerateNintendoSecondEncryptedDataTag(network_info, nodes); + + buffer.insert(buffer.end(), network_info_tag.begin(), network_info_tag.end()); + buffer.insert(buffer.end(), first_data_tag.begin(), first_data_tag.end()); + buffer.insert(buffer.end(), second_data_tag.begin(), second_data_tag.end()); + + return buffer; +} + +std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector<u8> buffer = GenerateFixedParameters(); + std::vector<u8> basic_tags = GenerateBasicTaggedParameters(); + std::vector<u8> nintendo_tags = GenerateNintendoTaggedParameters(network_info, nodes); + + buffer.insert(buffer.end(), basic_tags.begin(), basic_tags.end()); + buffer.insert(buffer.end(), nintendo_tags.begin(), nintendo_tags.end()); + + return buffer; +} + +std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) { + return {}; +} +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h new file mode 100644 index 000000000..6df4c4f47 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <deque> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NWM { + +using MacAddress = std::array<u8, 6>; + +/// The maximum number of nodes that can exist in an UDS session. +constexpr u32 UDSMaxNodes = 16; +constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; + +/// Additional block tag ids in the Beacon frames +enum class TagId : u8 { + SSID = 0, + SupportedRates = 1, + DSParameterSet = 2, + TrafficIndicationMap = 5, + CountryInformation = 7, + ERPInformation = 42, + VendorSpecific = 221 +}; + +/** + * Internal vendor-specific tag ids as stored inside + * VendorSpecific blocks in the Beacon frames. + */ +enum class NintendoTagId : u8 { + Dummy = 20, + NetworkInfo = 21, + EncryptedData0 = 24, + EncryptedData1 = 25, +}; + +struct BeaconEntryHeader { + u32_le total_size; + INSERT_PADDING_BYTES(1); + u8 wifi_channel; + INSERT_PADDING_BYTES(2); + MacAddress mac_address; + INSERT_PADDING_BYTES(6); + u32_le unk_size; + u32_le header_size; +}; + +static_assert(sizeof(BeaconEntryHeader) == 0x1C, "BeaconEntryHeader has incorrect size."); + +struct BeaconDataReplyHeader { + u32_le max_output_size; + u32_le total_size; + u32_le total_entries; +}; + +static_assert(sizeof(BeaconDataReplyHeader) == 12, "BeaconDataReplyHeader has incorrect size."); + +#pragma pack(push, 1) +struct BeaconFrameHeader { + // Number of microseconds the AP has been active. + u64_le timestamp; + // Interval between beacon transmissions, expressed in TU. + u16_le beacon_interval; + // Indicates the presence of optional capabilities. + u16_le capabilities; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconFrameHeader) == 12, "BeaconFrameHeader has incorrect size."); + +struct TagHeader { + u8 tag_id; + u8 length; +}; + +static_assert(sizeof(TagHeader) == 2, "TagHeader has incorrect size."); + +struct DummyTag { + TagHeader header; + std::array<u8, 3> oui; + u8 oui_type; + std::array<u8, 3> data; +}; + +static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); + +struct NetworkInfoTag { + TagHeader header; + std::array<u8, 0x1F> network_info; + std::array<u8, 0x14> sha_hash; + u8 appdata_size; +}; + +static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); + +struct EncryptedDataTag { + TagHeader header; + std::array<u8, 3> oui; + u8 oui_type; +}; + +static_assert(sizeof(EncryptedDataTag) == 6, "EncryptedDataTag has incorrect size."); + +#pragma pack(push, 1) +// The raw bytes of this structure are the CTR used in the encryption (AES-CTR) +// of the beacon data stored in the EncryptedDataTags. +struct BeaconDataCryptoCTR { + MacAddress host_mac; + u32_le wlan_comm_id; + u8 id; + INSERT_PADDING_BYTES(1); + u32_le network_id; +}; + +static_assert(sizeof(BeaconDataCryptoCTR) == 0x10, "BeaconDataCryptoCTR has incorrect size."); + +struct BeaconNodeInfo { + u64_be friend_code_seed; + std::array<u16_be, 10> username; + u16_be network_node_id; +}; + +static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); + +struct BeaconData { + std::array<u8, 0x10> md5_hash; + u16_be bitmask; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); + +/// Information about a received WiFi packet. +/// Acts as our own 802.11 header. +struct WifiPacket { + enum class PacketType { Beacon, Data }; + + PacketType type; ///< The type of 802.11 frame, Beacon / Data. + + /// Raw 802.11 frame data, starting at the management frame header for management frames. + std::vector<u8> data; + MacAddress transmitter_address; ///< Mac address of the transmitter. + MacAddress destination_address; ///< Mac address of the receiver. + u8 channel; ///< WiFi channel where this frame was transmitted. +}; + +/** + * Decrypts the beacon data buffer for the network described by `network_info`. + */ +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer); + +/** + * Generates an 802.11 beacon frame starting at the management frame header. + * This frame contains information about the network and its connected clients. + * @returns The generated frame. + */ +std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); + +/** + * Returns a list of received 802.11 frames from the specified sender + * matching the type since the last call. + */ +std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index e373ed47a..319e8c946 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -27,67 +27,72 @@ static bool shell_open; static bool battery_is_charging; -void GetAdapterState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +static bool pedometer_is_counting; - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. +void GetAdapterState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetShellState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = shell_open ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(shell_open); } void GetBatteryLevel(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = - static_cast<u32>(ChargeLevels::CompletelyFull); // Set to a completely full battery + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(ChargeLevels::CompletelyFull)); // Set to a completely full battery LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetBatteryChargeState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +void GetPedometerState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(pedometer_is_counting); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetTotalStepCount(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0); - // TODO: This function is only a stub, - // it returns 0 as the total step count - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetSoftwareClosedFlag(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x80F, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(false); LOG_WARNING(Service_PTM, "(STUBBED) called"); } @@ -121,6 +126,7 @@ void Init() { shell_open = true; battery_is_charging = true; + pedometer_is_counting = false; // Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't // exist diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 683fb445b..e17e59835 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -75,6 +75,14 @@ void GetBatteryLevel(Interface* self); void GetBatteryChargeState(Interface* self); /** + * PTM::GetPedometerState service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, 0 = not counting steps, 1 = counting steps. + */ +void GetPedometerState(Interface* self); + +/** * PTM::GetTotalStepCount service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp index e0b65ba89..696a58a36 100644 --- a/src/core/hle/service/ptm/ptm_u.cpp +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00060000, GetShellState, "GetShellState"}, {0x00070000, GetBatteryLevel, "GetBatteryLevel"}, {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, - {0x00090000, nullptr, "GetPedometerState"}, + {0x00090000, GetPedometerState, "GetPedometerState"}, {0x000A0042, nullptr, "GetStepHistoryEntry"}, {0x000B00C2, nullptr, "GetStepHistory"}, {0x000C0000, GetTotalStepCount, "GetTotalStepCount"}, diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 1a4e3efa8..beeb13ffa 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "common/swap.h" +#include "core/core.h" #include "core/file_sys/archive_selfncch.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" @@ -339,6 +340,8 @@ ResultStatus AppLoader_NCCH::Load() { LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); + Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id); + is_loaded = true; // Set state to loaded result = LoadExec(); // Load the executable into memory for booting diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 65e4bba85..b8438e490 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -672,12 +672,14 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) { return addr - VRAM_VADDR + VRAM_PADDR; } else if (addr >= LINEAR_HEAP_VADDR && addr < LINEAR_HEAP_VADDR_END) { return addr - LINEAR_HEAP_VADDR + FCRAM_PADDR; + } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { + return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR; } else if (addr >= DSP_RAM_VADDR && addr < DSP_RAM_VADDR_END) { 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 >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { - return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR; + } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) { + return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR; } LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); @@ -696,6 +698,8 @@ 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; } LOG_ERROR(HW_Memory, "Unknown physical address @ 0x%08X", addr); diff --git a/src/core/memory.h b/src/core/memory.h index 903b58a22..802aa465e 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -37,6 +37,12 @@ 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) @@ -81,6 +87,10 @@ 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, diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 3d22c0afa..d2e7c6b97 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,7 +5,7 @@ #include "audio_core/audio_core.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" -#include "core/hle/service/ir/ir_user.h" +#include "core/hle/service/ir/ir.h" #include "settings.h" #include "video_core/video_core.h" diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp new file mode 100644 index 000000000..ddc8b262e --- /dev/null +++ b/src/core/telemetry_session.cpp @@ -0,0 +1,42 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "common/scm_rev.h" +#include "core/telemetry_session.h" + +namespace Core { + +TelemetrySession::TelemetrySession() { + // TODO(bunnei): Replace with a backend that logs to our web service + backend = std::make_unique<Telemetry::NullVisitor>(); + + // Log one-time session start information + const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; + const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; + AddField(Telemetry::FieldType::Session, "StartTime", start_time); + + // Log one-time application information + const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; + AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty); + AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch); + AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev); +} + +TelemetrySession::~TelemetrySession() { + // Log one-time session end information + const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; + const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; + AddField(Telemetry::FieldType::Session, "EndTime", end_time); + + // Complete the session, submitting to web service if necessary + // This is just a placeholder to wrap up the session once the core completes and this is + // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service. + field_collection.Accept(*backend); + backend->Complete(); + backend = nullptr; +} + +} // namespace Core diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h new file mode 100644 index 000000000..cf53835c3 --- /dev/null +++ b/src/core/telemetry_session.h @@ -0,0 +1,38 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/telemetry.h" + +namespace Core { + +/** + * Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each + * session, logging any one-time fields. Interfaces with the telemetry backend used for submitting + * data to the web service. Submits session data on close. + */ +class TelemetrySession : NonCopyable { +public: + TelemetrySession(); + ~TelemetrySession(); + + /** + * Wrapper around the Telemetry::FieldCollection::AddField method. + * @param type Type of the field to add. + * @param name Name of the field to add. + * @param value Value for the field to add. + */ + template <typename T> + void AddField(Telemetry::FieldType type, const char* name, T value) { + field_collection.AddField(type, name, std::move(value)); + } + +private: + Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session + std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields +}; + +} // namespace Core diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 49a93e980..4633a1df1 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -32,12 +32,13 @@ namespace Pica { namespace CommandProcessor { -static int float_regs_counter = 0; +static int vs_float_regs_counter = 0; +static u32 vs_uniform_write_buffer[4]; -static u32 uniform_write_buffer[4]; +static int gs_float_regs_counter = 0; +static u32 gs_uniform_write_buffer[4]; static int default_attr_counter = 0; - static u32 default_attr_write_buffer[3]; // Expand a 4-bit mask to 4-byte mask, e.g. 0b0101 -> 0x00FF00FF @@ -48,6 +49,97 @@ static const u32 expand_bits_to_bytes[] = { MICROPROFILE_DEFINE(GPU_Drawing, "GPU", "Drawing", MP_RGB(50, 50, 240)); +static const char* GetShaderSetupTypeName(Shader::ShaderSetup& setup) { + if (&setup == &g_state.vs) { + return "vertex shader"; + } + if (&setup == &g_state.gs) { + return "geometry shader"; + } + return "unknown shader"; +} + +static void WriteUniformBoolReg(Shader::ShaderSetup& setup, u32 value) { + for (unsigned i = 0; i < setup.uniforms.b.size(); ++i) + setup.uniforms.b[i] = (value & (1 << i)) != 0; +} + +static void WriteUniformIntReg(Shader::ShaderSetup& setup, unsigned index, + const Math::Vec4<u8>& values) { + ASSERT(index < setup.uniforms.i.size()); + setup.uniforms.i[index] = values; + LOG_TRACE(HW_GPU, "Set %s integer uniform %d to %02x %02x %02x %02x", + GetShaderSetupTypeName(setup), index, values.x, values.y, values.z, values.w); +} + +static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup, + int& float_regs_counter, u32 uniform_write_buffer[4], u32 value) { + auto& uniform_setup = config.uniform_setup; + + // TODO: Does actual hardware indeed keep an intermediate buffer or does + // it directly write the values? + uniform_write_buffer[float_regs_counter++] = value; + + // Uniforms are written in a packed format such that four float24 values are encoded in + // three 32-bit numbers. We write to internal memory once a full such vector is + // written. + if ((float_regs_counter >= 4 && uniform_setup.IsFloat32()) || + (float_regs_counter >= 3 && !uniform_setup.IsFloat32())) { + float_regs_counter = 0; + + auto& uniform = setup.uniforms.f[uniform_setup.index]; + + if (uniform_setup.index >= 96) { + LOG_ERROR(HW_GPU, "Invalid %s float uniform index %d", GetShaderSetupTypeName(setup), + (int)uniform_setup.index); + } else { + + // NOTE: The destination component order indeed is "backwards" + if (uniform_setup.IsFloat32()) { + for (auto i : {0, 1, 2, 3}) + uniform[3 - i] = float24::FromFloat32(*(float*)(&uniform_write_buffer[i])); + } else { + // TODO: Untested + uniform.w = float24::FromRaw(uniform_write_buffer[0] >> 8); + uniform.z = float24::FromRaw(((uniform_write_buffer[0] & 0xFF) << 16) | + ((uniform_write_buffer[1] >> 16) & 0xFFFF)); + uniform.y = float24::FromRaw(((uniform_write_buffer[1] & 0xFFFF) << 8) | + ((uniform_write_buffer[2] >> 24) & 0xFF)); + uniform.x = float24::FromRaw(uniform_write_buffer[2] & 0xFFFFFF); + } + + LOG_TRACE(HW_GPU, "Set %s float uniform %x to (%f %f %f %f)", + GetShaderSetupTypeName(setup), (int)uniform_setup.index, + uniform.x.ToFloat32(), uniform.y.ToFloat32(), uniform.z.ToFloat32(), + uniform.w.ToFloat32()); + + // TODO: Verify that this actually modifies the register! + uniform_setup.index.Assign(uniform_setup.index + 1); + } + } +} + +static void WriteProgramCode(ShaderRegs& config, Shader::ShaderSetup& setup, + unsigned max_program_code_length, u32 value) { + if (config.program.offset >= max_program_code_length) { + LOG_ERROR(HW_GPU, "Invalid %s program offset %d", GetShaderSetupTypeName(setup), + (int)config.program.offset); + } else { + setup.program_code[config.program.offset] = value; + config.program.offset++; + } +} + +static void WriteSwizzlePatterns(ShaderRegs& config, Shader::ShaderSetup& setup, u32 value) { + if (config.swizzle_patterns.offset >= setup.swizzle_data.size()) { + LOG_ERROR(HW_GPU, "Invalid %s swizzle pattern offset %d", GetShaderSetupTypeName(setup), + (int)config.swizzle_patterns.offset); + } else { + setup.swizzle_data[config.swizzle_patterns.offset] = value; + config.swizzle_patterns.offset++; + } +} + static void WritePicaReg(u32 id, u32 value, u32 mask) { auto& regs = g_state.regs; @@ -330,21 +422,70 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { break; } - case PICA_REG_INDEX(vs.bool_uniforms): - for (unsigned i = 0; i < 16; ++i) - g_state.vs.uniforms.b[i] = (regs.vs.bool_uniforms.Value() & (1 << i)) != 0; + case PICA_REG_INDEX(gs.bool_uniforms): + WriteUniformBoolReg(g_state.gs, g_state.regs.gs.bool_uniforms.Value()); + break; + case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[0], 0x281): + case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[1], 0x282): + case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[2], 0x283): + case PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[3], 0x284): { + unsigned index = (id - PICA_REG_INDEX_WORKAROUND(gs.int_uniforms[0], 0x281)); + auto values = regs.gs.int_uniforms[index]; + WriteUniformIntReg(g_state.gs, index, + Math::Vec4<u8>(values.x, values.y, values.z, values.w)); + break; + } + + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[0], 0x291): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[1], 0x292): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[2], 0x293): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[3], 0x294): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[4], 0x295): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[5], 0x296): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[6], 0x297): + case PICA_REG_INDEX_WORKAROUND(gs.uniform_setup.set_value[7], 0x298): { + WriteUniformFloatReg(g_state.regs.gs, g_state.gs, gs_float_regs_counter, + gs_uniform_write_buffer, value); + break; + } + + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[0], 0x29c): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[1], 0x29d): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[2], 0x29e): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[3], 0x29f): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[4], 0x2a0): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[5], 0x2a1): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[6], 0x2a2): + case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[7], 0x2a3): { + WriteProgramCode(g_state.regs.gs, g_state.gs, 4096, value); + break; + } + + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[0], 0x2a6): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[1], 0x2a7): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[2], 0x2a8): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[3], 0x2a9): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[4], 0x2aa): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[5], 0x2ab): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[6], 0x2ac): + case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[7], 0x2ad): { + WriteSwizzlePatterns(g_state.regs.gs, g_state.gs, value); + break; + } + + case PICA_REG_INDEX(vs.bool_uniforms): + WriteUniformBoolReg(g_state.vs, g_state.regs.vs.bool_uniforms.Value()); break; case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1): case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[1], 0x2b2): case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[2], 0x2b3): case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[3], 0x2b4): { - int index = (id - PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1)); + unsigned index = (id - PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1)); auto values = regs.vs.int_uniforms[index]; - g_state.vs.uniforms.i[index] = Math::Vec4<u8>(values.x, values.y, values.z, values.w); - LOG_TRACE(HW_GPU, "Set integer uniform %d to %02x %02x %02x %02x", index, values.x.Value(), - values.y.Value(), values.z.Value(), values.w.Value()); + WriteUniformIntReg(g_state.vs, index, + Math::Vec4<u8>(values.x, values.y, values.z, values.w)); break; } @@ -356,51 +497,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[5], 0x2c6): case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[6], 0x2c7): case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[7], 0x2c8): { - auto& uniform_setup = regs.vs.uniform_setup; - - // TODO: Does actual hardware indeed keep an intermediate buffer or does - // it directly write the values? - uniform_write_buffer[float_regs_counter++] = value; - - // Uniforms are written in a packed format such that four float24 values are encoded in - // three 32-bit numbers. We write to internal memory once a full such vector is - // written. - if ((float_regs_counter >= 4 && uniform_setup.IsFloat32()) || - (float_regs_counter >= 3 && !uniform_setup.IsFloat32())) { - float_regs_counter = 0; - - auto& uniform = g_state.vs.uniforms.f[uniform_setup.index]; - - if (uniform_setup.index > 95) { - LOG_ERROR(HW_GPU, "Invalid VS uniform index %d", (int)uniform_setup.index); - break; - } - - // NOTE: The destination component order indeed is "backwards" - if (uniform_setup.IsFloat32()) { - for (auto i : {0, 1, 2, 3}) - uniform[3 - i] = float24::FromFloat32(*(float*)(&uniform_write_buffer[i])); - } else { - // TODO: Untested - uniform.w = float24::FromRaw(uniform_write_buffer[0] >> 8); - uniform.z = float24::FromRaw(((uniform_write_buffer[0] & 0xFF) << 16) | - ((uniform_write_buffer[1] >> 16) & 0xFFFF)); - uniform.y = float24::FromRaw(((uniform_write_buffer[1] & 0xFFFF) << 8) | - ((uniform_write_buffer[2] >> 24) & 0xFF)); - uniform.x = float24::FromRaw(uniform_write_buffer[2] & 0xFFFFFF); - } - - LOG_TRACE(HW_GPU, "Set uniform %x to (%f %f %f %f)", (int)uniform_setup.index, - uniform.x.ToFloat32(), uniform.y.ToFloat32(), uniform.z.ToFloat32(), - uniform.w.ToFloat32()); - - // TODO: Verify that this actually modifies the register! - uniform_setup.index.Assign(uniform_setup.index + 1); - } + WriteUniformFloatReg(g_state.regs.vs, g_state.vs, vs_float_regs_counter, + vs_uniform_write_buffer, value); break; } - // Load shader program code case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[0], 0x2cc): case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[1], 0x2cd): case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[2], 0x2ce): @@ -409,12 +510,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[5], 0x2d1): case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[6], 0x2d2): case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[7], 0x2d3): { - g_state.vs.program_code[regs.vs.program.offset] = value; - regs.vs.program.offset++; + WriteProgramCode(g_state.regs.vs, g_state.vs, 512, value); break; } - // Load swizzle pattern data case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[0], 0x2d6): case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[1], 0x2d7): case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[2], 0x2d8): @@ -423,8 +522,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[5], 0x2db): case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[6], 0x2dc): case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[7], 0x2dd): { - g_state.vs.swizzle_data[regs.vs.swizzle_patterns.offset] = value; - regs.vs.swizzle_patterns.offset++; + WriteSwizzlePatterns(g_state.regs.vs, g_state.vs, value); break; } diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h index 9ddc79243..a50bd4111 100644 --- a/src/video_core/regs_framebuffer.h +++ b/src/video_core/regs_framebuffer.h @@ -211,13 +211,14 @@ struct FramebufferRegs { BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable }; - DepthFormat depth_format; // TODO: Should be a BitField! + BitField<0, 2, DepthFormat> depth_format; + BitField<16, 3, ColorFormat> color_format; INSERT_PADDING_WORDS(0x4); - u32 depth_buffer_address; - u32 color_buffer_address; + BitField<0, 28, u32> depth_buffer_address; + BitField<0, 28, u32> color_buffer_address; union { // Apparently, the framebuffer width is stored as expected, diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h index 0a4ec6e1e..31c747d77 100644 --- a/src/video_core/regs_pipeline.h +++ b/src/video_core/regs_pipeline.h @@ -22,10 +22,10 @@ struct PipelineRegs { }; struct { - BitField<0, 29, u32> base_address; + BitField<1, 28, u32> base_address; PAddr GetPhysicalBaseAddress() const { - return base_address * 8; + return base_address * 16; } // Descriptor for internal vertex attributes @@ -99,7 +99,7 @@ struct PipelineRegs { // This e.g. allows to load different attributes from different memory locations struct { // Source attribute data offset from the base address - u32 data_offset; + BitField<0, 28, u32> data_offset; union { BitField<0, 4, u32> comp0; @@ -180,6 +180,8 @@ struct PipelineRegs { // kicked off. // 2) Games can configure these registers to provide a command list subroutine mechanism. + // TODO: verify the bit length of these two fields + // According to 3dbrew, the bit length of them are 21 and 29, respectively BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h index a471a3b38..2874fd127 100644 --- a/src/video_core/regs_rasterizer.h +++ b/src/video_core/regs_rasterizer.h @@ -92,13 +92,13 @@ struct RasterizerRegs { BitField<0, 2, ScissorMode> mode; union { - BitField<0, 16, u32> x1; - BitField<16, 16, u32> y1; + BitField<0, 10, u32> x1; + BitField<16, 10, u32> y1; }; union { - BitField<0, 16, u32> x2; - BitField<16, 16, u32> y2; + BitField<0, 10, u32> x2; + BitField<16, 10, u32> y2; }; } scissor_test; diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index 20f9495ed..e4038b41b 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -29,6 +29,11 @@ struct TexturingRegs { ClampToBorder = 1, Repeat = 2, MirroredRepeat = 3, + // Mode 4-7 produces some weird result and may be just invalid: + // 4: Positive coord: clamp to edge; negative coord: repeat + // 5: Positive coord: clamp to border; negative coord: repeat + // 6: Repeat + // 7: Repeat }; enum TextureFilter : u32 { @@ -45,22 +50,22 @@ struct TexturingRegs { } border_color; union { - BitField<0, 16, u32> height; - BitField<16, 16, u32> width; + BitField<0, 11, u32> height; + BitField<16, 11, u32> width; }; union { BitField<1, 1, TextureFilter> mag_filter; BitField<2, 1, TextureFilter> min_filter; - BitField<8, 2, WrapMode> wrap_t; - BitField<12, 2, WrapMode> wrap_s; - BitField<28, 2, TextureType> - type; ///< @note Only valid for texture 0 according to 3DBrew. + BitField<8, 3, WrapMode> wrap_t; + BitField<12, 3, WrapMode> wrap_s; + /// @note Only valid for texture 0 according to 3DBrew. + BitField<28, 3, TextureType> type; }; INSERT_PADDING_WORDS(0x1); - u32 address; + BitField<0, 28, u32> address; PAddr GetPhysicalAddress() const { return address * 8; diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 38ea717ab..e156f6aef 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -24,6 +24,9 @@ namespace Pica { namespace Shader { +constexpr unsigned MAX_PROGRAM_CODE_LENGTH = 4096; +constexpr unsigned MAX_SWIZZLE_DATA_LENGTH = 4096; + struct AttributeBuffer { alignas(16) Math::Vec4<float24> attr[16]; }; @@ -144,8 +147,8 @@ struct ShaderSetup { return offsetof(ShaderSetup, uniforms.i) + index * sizeof(Math::Vec4<u8>); } - std::array<u32, 1024> program_code; - std::array<u32, 1024> swizzle_data; + std::array<u32, MAX_PROGRAM_CODE_LENGTH> program_code; + std::array<u32, MAX_SWIZZLE_DATA_LENGTH> swizzle_data; /// Data private to ShaderEngines struct EngineData { diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index f4d1c46c5..aa1cec81f 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -653,7 +653,7 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData } void InterpreterEngine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { - ASSERT(entry_point < 1024); + ASSERT(entry_point < MAX_PROGRAM_CODE_LENGTH); setup.engine_data.entry_point = entry_point; } diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index 0ee0dd9ef..73c21871c 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -15,7 +15,7 @@ JitX64Engine::JitX64Engine() = default; JitX64Engine::~JitX64Engine() = default; void JitX64Engine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) { - ASSERT(entry_point < 1024); + ASSERT(entry_point < MAX_PROGRAM_CODE_LENGTH); setup.engine_data.entry_point = entry_point; u64 code_hash = Common::ComputeHash64(&setup.program_code, sizeof(setup.program_code)); diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 2dbc8b147..5d9b6448c 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -834,8 +834,8 @@ void JitShader::FindReturnOffsets() { std::sort(return_offsets.begin(), return_offsets.end()); } -void JitShader::Compile(const std::array<u32, 1024>* program_code_, - const std::array<u32, 1024>* swizzle_data_) { +void JitShader::Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code_, + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data_) { program_code = program_code_; swizzle_data = swizzle_data_; diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h index f27675560..31af0ca48 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.h +++ b/src/video_core/shader/shader_jit_x64_compiler.h @@ -22,8 +22,8 @@ namespace Pica { namespace Shader { -/// Memory allocated for each compiled shader (64Kb) -constexpr size_t MAX_SHADER_SIZE = 1024 * 64; +/// Memory allocated for each compiled shader +constexpr size_t MAX_SHADER_SIZE = MAX_PROGRAM_CODE_LENGTH * 64; /** * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 @@ -37,8 +37,8 @@ public: program(&setup, &state, instruction_labels[offset].getAddress()); } - void Compile(const std::array<u32, 1024>* program_code, - const std::array<u32, 1024>* swizzle_data); + void Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code, + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data); void Compile_ADD(Instruction instr); void Compile_DP3(Instruction instr); @@ -104,11 +104,11 @@ private: */ void FindReturnOffsets(); - const std::array<u32, 1024>* program_code = nullptr; - const std::array<u32, 1024>* swizzle_data = nullptr; + const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_code = nullptr; + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>* swizzle_data = nullptr; /// Mapping of Pica VS instructions to pointers in the emitted code - std::array<Xbyak::Label, 1024> instruction_labels; + std::array<Xbyak::Label, MAX_PROGRAM_CODE_LENGTH> instruction_labels; /// Offsets in code where a return needs to be inserted std::vector<unsigned> return_offsets; diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index 2d80822d9..6fb923756 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -69,13 +69,14 @@ static void InitScreenCoordinates(Vertex& vtx) { viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.y)); float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w; - vtx.color *= inv_w; - vtx.view *= inv_w; + vtx.pos.w = inv_w; vtx.quat *= inv_w; + vtx.color *= inv_w; vtx.tc0 *= inv_w; vtx.tc1 *= inv_w; + vtx.tc0_w *= inv_w; + vtx.view *= inv_w; vtx.tc2 *= inv_w; - vtx.pos.w = inv_w; vtx.screenpos[0] = (vtx.pos.x * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_x + viewport.offset_x; diff --git a/src/video_core/swrasterizer/rasterizer.h b/src/video_core/swrasterizer/rasterizer.h index 3a72ac343..2f0877581 100644 --- a/src/video_core/swrasterizer/rasterizer.h +++ b/src/video_core/swrasterizer/rasterizer.h @@ -23,13 +23,15 @@ struct Vertex : Shader::OutputVertex { pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); // TODO: Should perform perspective correct interpolation here... + quat = quat * factor + vtx.quat * (float24::FromFloat32(1) - factor); + color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor); tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor); + tc0_w = tc0_w * factor + vtx.tc0_w * (float24::FromFloat32(1) - factor); + view = view * factor + vtx.view * (float24::FromFloat32(1) - factor); tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor); screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor); - - color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); } // Linear interpolation |