Compare commits

..

4 Commits

Author SHA1 Message Date
lizzie ae47c184d4 s^2 2026-09-09 05:22:17 +02:00
lizzie 913be2fa49 fix linking issues 2026-09-09 05:22:17 +02:00
lizzie 55e07429a0 remove latency func 2026-09-09 05:22:17 +02:00
lizzie 1af01cc881 [audio] Nuke cubeb and use SDL3 exclusively
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-09 05:22:17 +02:00
78 changed files with 794 additions and 1908 deletions
-7
View File
@@ -53,7 +53,6 @@ if (YUZU_STATIC_ROOM)
# disable e v e r y t h i n g
set(ENABLE_QT OFF)
set(YUZU_CMD OFF)
set(ENABLE_CUBEB OFF)
set(ENABLE_UPDATE_CHECKER OFF)
set(USE_DISCORD_PRESENCE OFF)
set(BUILD_TESTING OFF)
@@ -215,8 +214,6 @@ if(MSVC)
endif()
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
set(EXT_DEFAULT OFF)
if (MSVC OR ANDROID)
set(EXT_DEFAULT ON)
@@ -575,10 +572,6 @@ if (NOT YUZU_STATIC_ROOM)
find_package(DiscordRPC)
endif()
if (ENABLE_CUBEB)
find_package(cubeb)
endif()
if (YUZU_TESTS OR DYNARMIC_TESTS)
find_package(Catch2)
endif()
-12
View File
@@ -46,18 +46,6 @@
"repo": "arun11299/cpp-jwt",
"version": "7f24eb4c32"
},
"cubeb": {
"find_args": "CONFIG",
"hash": "8a4bcb2f83ba590f52c66626e895304a73eb61928dbc57777e1822e55378e3568366f17f9da4b80036cc2ef4ea9723c32abf6e7d9bbe00fb03654f0991596ab0",
"options": [
"USE_SANITIZERS OFF",
"BUILD_TESTS OFF",
"BUILD_TOOLS OFF",
"BUNDLE_SPEEX ON"
],
"repo": "mozilla/cubeb",
"version": "fa02160712"
},
"discord-rpc": {
"find_args": "MODULE",
"hash": "8d680b3a16d6f6bf292ad823cf8635595ff986f2a49f758e3009f505b540a9d75194a8cf44cddea75736a8c3e3b5160166e716a0939ce3f18cc463cf648753fe",
-2
View File
@@ -100,8 +100,6 @@ For reasons unberknownst to any human being, `glslangValidator` will crash upon
For this reason this patch is NOT applied to default on all platforms (for obvious reasons) - instead this is a HaikuOS specific patch, apply with `git apply <absolute path to patch>` after cloning SPIRV-Tools then `make -C build` and add the resulting binary (in `build/StandAlone/glslang`) into PATH.
`cubeb_devel` will also not work, either disable cubeb or uninstall it.
Still will not run flawlessly until `mesa-24` is available. Modify CMakeCache.txt with the `.so` of libGL and libGLESv2 by doing the incredibly difficult task of copy pasting them (`cp /boot/system/lib/libGL.so .`)
If you have `quazip1_qt6_devel`, uninstall it. It may call `Core5Compat` on CMake which is wrongly packaged.
+2 -3
View File
@@ -73,7 +73,6 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
* [SimpleIni](https://github.com/brofield/simpleini)
* [DiscordRPC](https://github.com/eden-emulator/discord-rpc)
* [cubeb](https://github.com/mozilla/cubeb)
* [libusb](https://github.com/libusb/libusb)
* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
* [sirit](https://github.com/eden-emulator/sirit)
@@ -122,7 +121,7 @@ sudo emerge -a \
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
dev-util/vulkan-utility-libraries dev-util/glslang \
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
media-libs/VulkanMemoryAllocator media-libs/libsdl3 \
net-libs/enet \
sys-libs/zlib \
dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
<summary>HaikuOS</summary>
```sh
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel simpleini quazip_qt6_devel
```
[Caveats](./Caveats.md#haikuos).
+1 -1
View File
@@ -4,7 +4,7 @@ Modern game consoles require heavy power to be emulated appropriately. This is w
For example, take a disk write, instead of emulating a proper SD card we instead use the C++ standard library for I/O. Additionally we use the abstractions provided by the `fs` service to "lie" to programs about certain SD card properties. Notably this includes making up sizes for the fake SD card, giving "realistic" values or expected outputs for a given card, and so on. And instead of writing to an actual SD card, the emulator simply writes to a file.
This also means grand part of the emulator consists of just re-implementing firmware but using HLE primitives; for example audio doesn't go to an emulated audio device, but rather gets processed on the fly by a dedicated service and then passed to SDL3/cubeb/etc.
This also means grand part of the emulator consists of just re-implementing firmware but using HLE primitives; for example audio doesn't go to an emulated audio device, but rather gets processed on the fly by a dedicated service and then passed to SDL3 or null backend.
As such, many of the systems implemented are not 100% accurate to the original software, but they're "good enough" to pass as being so. While we do strive to maintain high compatibility (especially with homebrew), there are realistic limitations to these approaches.
-2
View File
@@ -35,8 +35,6 @@ These options control dependencies.
- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service
- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental
- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend
- This option is subject for removal.
- `YUZU_TESTS` (ON) Compile tests - requires Catch2
- `ENABLE_LTO` (OFF) Enable link-time optimization
- Not recommended on Windows
-30
View File
@@ -106,36 +106,6 @@ endif()
# SimpleIni
AddJsonPackage(simpleini)
# Most linux distros don't package cubeb, so enable regardless of cpm settings
if(ENABLE_CUBEB)
AddJsonPackage(cubeb)
if (cubeb_ADDED)
if (NOT MSVC)
if (TARGET speex)
target_compile_options(speex PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-compare>)
endif()
set_target_properties(cubeb PROPERTIES COMPILE_OPTIONS "")
target_compile_options(cubeb INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-const-int-float-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-return-type>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-uninitialized>
)
else()
target_compile_options(cubeb PRIVATE
/wd4456
/wd4458
)
endif()
endif()
if (NOT TARGET cubeb::cubeb)
add_library(cubeb::cubeb ALIAS cubeb)
endif()
endif()
if (NOT YUZU_USE_BUNDLED_SDL3)
if (NOT WIN32)
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
+1 -1
View File
@@ -15,7 +15,7 @@ pkgs.mkShellNoCC {
enet libopus vulkan-headers vulkan-utility-libraries
spirv-tools spirv-headers vulkan-loader unzip
glslang python3 httplib cpp-jwt ffmpeg-headless
libusb1 cubeb
libusb1
# eden
qt6.qtbase qt6.qtmultimedia qt6.qtwayland qt6.qttools
qt6.qtwebengine qt6.qt5compat
@@ -58,13 +58,6 @@ class LicensesFragment : Fragment() {
R.string.license_fidelityfx_fsr_copyright,
R.string.license_fidelityfx_fsr_text
),
License(
R.string.license_cubeb,
R.string.license_cubeb_description,
R.string.license_cubeb_link,
R.string.license_cubeb_copyright,
R.string.license_cubeb_text
),
License(
R.string.license_dynarmic,
R.string.license_dynarmic_description,
@@ -799,9 +799,6 @@
<string name="theme_mode_light">روشن</string>
<string name="theme_mode_dark">تاریک</string>
<!-- Audio output engines -->
<string name="cubeb">cubeb</string>
<!-- Anisotropic filtering options -->
<string name="multiplier_x2">x2</string>
<string name="multiplier_x4">x4</string>
@@ -466,13 +466,11 @@
<string-array name="outputEngineEntries">
<item>@string/auto</item>
<item>@string/sdl3</item>
<item>@string/cubeb</item>
<item>@string/string_null</item>
</string-array>
<integer-array name="outputEngineValues">
<item>0</item>
<item>2</item>
<item>1</item>
<item>3</item>
</integer-array>
@@ -3,7 +3,7 @@
<!-- General application strings -->
<string name="app_name" translatable="false">Eden</string>
<string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/QuickStart.md\">Learn more</a>]]></string>
<string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-mirror.github.io/help/quickstart\">Learn more</a>]]></string>
<string name="notice_notification_channel_name">Notices and errors</string>
<string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
@@ -933,7 +933,7 @@
<string name="loader_error_file_not_found">ROM file does not exist</string>
<string name="loader_requires_firmware">Game Requires Firmware</string>
<string name="loader_requires_firmware_description"><![CDATA[This game may require firmware to function properly, and you don\'t have any installed. Please install firmware before launching, or press \"OK\" to launch anyways.]]></string>
<string name="loader_requires_firmware_description"><![CDATA[The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href=\"https://yuzu-mirror.github.io/help/quickstart\"> dump and install firmware</a>, or press \"OK\" to launch anyways.]]></string>
<!-- Intent Launch strings -->
<string name="searching_for_game">Searching for game...</string>
@@ -1243,7 +1243,6 @@
<!-- Audio output engines -->
<string name="sdl3" translatable="false">SDL3</string>
<string name="cubeb" translatable="false">cubeb</string>
<!-- Anisotropic filtering options -->
<string name="multiplier_x2" translatable="false">x2</string>
@@ -1383,23 +1382,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<string name="license_cubeb" translatable="false">cubeb</string>
<string name="license_cubeb_description" translatable="false">Cross platform audio library</string>
<string name="license_cubeb_link" translatable="false">https://github.com/mozilla/cubeb</string>
<string name="license_cubeb_copyright" translatable="false">Copyright © 2011 Mozilla Foundation</string>
<string name="license_cubeb_text" translatable="false">
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.\n\n
THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</string>
<string name="license_dynarmic" translatable="false">Dynarmic</string>
<string name="license_dynarmic_description" translatable="false">An ARM dynamic recompiler</string>
+4 -9
View File
@@ -229,15 +229,6 @@ endif()
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(audio_core PUBLIC common core Opus::opus)
if (ENABLE_CUBEB)
target_sources(audio_core PRIVATE
sink/cubeb_sink.cpp
sink/cubeb_sink.h)
target_link_libraries(audio_core PRIVATE cubeb::cubeb)
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
endif()
target_sources(audio_core PRIVATE
sink/sdl3_sink.cpp
sink/sdl3_sink.h)
@@ -245,4 +236,8 @@ target_sources(audio_core PRIVATE
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
if (ANDROID)
target_link_libraries(audio_core PRIVATE OpenSLES)
endif()
create_target_directory_groups(audio_core)
-16
View File
@@ -8,7 +8,6 @@
#include "audio_core/opus/hardware_opus.h"
#include "audio_core/opus/parameters.h"
#include "common/alignment.h"
#include "common/scope_exit.h"
#include "common/swap.h"
#include "core/core.h"
@@ -29,18 +28,10 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
OpusDecoder::~OpusDecoder() {
if (decode_object_initialized) {
hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size());
hardware_opus.UnregisterDecoder(this);
}
}
Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
R_TRY(hardware_opus.RegisterDecoder(this));
SCOPE_EXIT {
if (!decode_object_initialized) {
hardware_opus.UnregisterDecoder(this);
}
};
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
shared_buffer.resize(transfer_memory_size);
shared_memory_mapped = true;
@@ -70,13 +61,6 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransfer
}
Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
R_TRY(hardware_opus.RegisterDecoder(this));
SCOPE_EXIT {
if (!decode_object_initialized) {
hardware_opus.UnregisterDecoder(this);
}
};
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
shared_buffer.resize(transfer_memory_size, 0);
shared_memory_mapped = true;
-20
View File
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include "audio_core/audio_core.h"
@@ -46,25 +45,6 @@ HardwareOpus::HardwareOpus(Core::System& system_)
opus_decoder.SetSharedMemory(shared_memory);
}
Result HardwareOpus::RegisterDecoder(OpusDecoder* decoder) {
std::scoped_lock l{mutex};
const auto slot = std::ranges::find(decoders, nullptr);
if (slot == decoders.end()) {
R_THROW(ResultOutOfOpusDecoders);
}
*slot = decoder;
R_SUCCEED();
}
void HardwareOpus::UnregisterDecoder(OpusDecoder* decoder) {
std::scoped_lock l{mutex};
const auto slot = std::ranges::find(decoders, decoder);
if (slot == decoders.end()) {
return;
}
*slot = nullptr;
}
u32 HardwareOpus::GetWorkBufferSize(u32 channel) {
if (!opus_decoder.IsRunning()) {
return 0;
-5
View File
@@ -3,7 +3,6 @@
#pragma once
#include <array>
#include <mutex>
#include <opus.h>
@@ -13,12 +12,9 @@
#include "core/hle/service/audio/errors.h"
namespace AudioCore::OpusDecoder {
class OpusDecoder;
class HardwareOpus {
public:
HardwareOpus(Core::System& system);
Result RegisterDecoder(OpusDecoder* decoder);
void UnregisterDecoder(OpusDecoder* decoder);
u32 GetWorkBufferSize(u32 channel);
u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
@@ -43,7 +39,6 @@ public:
private:
Core::System& system;
std::mutex mutex;
std::array<OpusDecoder*, 24> decoders{};
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
ADSP::OpusDecoder::SharedMemory shared_memory;
};
-450
View File
@@ -1,450 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <vector>
#include "audio_core/common/common.h"
#include "audio_core/sink/cubeb_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging.h"
#include "common/scope_exit.h"
#include "core/core.h"
#ifdef _WIN32
#include <objbase.h>
#undef CreateEvent
#endif
namespace AudioCore::Sink {
/**
* Cubeb sink stream, responsible for sinking samples to hardware.
*/
class CubebSinkStream final : public SinkStream {
public:
/**
* Create a new sink stream.
*
* @param ctx_ - Cubeb context to create this stream with.
* @param device_channels_ - Number of channels supported by the hardware.
* @param system_channels_ - Number of channels the audio systems expect.
* @param output_device - Cubeb output device id.
* @param input_device - Cubeb input device id.
* @param name_ - Name of this stream.
* @param type_ - Type of this stream.
* @param system_ - Core system.
* @param event - Event used only for audio renderer, signalled on buffer consume.
*/
CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
StreamType type_, Core::System& system_)
: SinkStream(system_, type_), ctx{ctx_} {
#ifdef _WIN32
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
name = name_;
device_channels = device_channels_;
system_channels = system_channels_;
cubeb_stream_params params{};
params.rate = TargetSampleRate;
params.channels = device_channels;
params.format = CUBEB_SAMPLE_S16LE;
params.prefs = CUBEB_STREAM_PREF_NONE;
switch (params.channels) {
case 1:
params.layout = CUBEB_LAYOUT_MONO;
break;
case 2:
params.layout = CUBEB_LAYOUT_STEREO;
break;
case 6:
params.layout = CUBEB_LAYOUT_3F2_LFE;
break;
}
u32 minimum_latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &minimum_latency);
if (latency_error != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
minimum_latency = TargetSampleCount * 2;
}
minimum_latency = (std::max)(minimum_latency, TargetSampleCount * 2);
LOG_INFO(Service_Audio,
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
"latency {}",
name, type, params.rate, params.channels, system_channels, minimum_latency);
auto init_error{0};
if (type == StreamType::In) {
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
&params, output_device, nullptr, minimum_latency,
&CubebSinkStream::DataCallback,
&CubebSinkStream::StateCallback, this);
} else {
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
nullptr, output_device, &params, minimum_latency,
&CubebSinkStream::DataCallback,
&CubebSinkStream::StateCallback, this);
}
if (init_error != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
return;
}
}
/**
* Destroy the sink stream.
*/
~CubebSinkStream() override {
LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
if (!ctx) {
return;
}
Finalize();
#ifdef _WIN32
CoUninitialize();
#endif
}
/**
* Finalize the sink stream.
*/
void Finalize() override {
Stop();
cubeb_stream_destroy(stream_backend);
}
/**
* Start the sink stream.
*
* @param resume - Set to true if this is resuming the stream a previously-active stream.
* Default false.
*/
void Start(bool resume = false) override {
if (!ctx || !paused) {
return;
}
paused = false;
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
}
}
/**
* Stop the sink stream.
*/
void Stop() override {
if (!ctx || paused) {
return;
}
SignalPause();
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
}
}
private:
/**
* Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
* provide samples to be copied (audio in).
*
* @param stream - Cubeb-specific data about the stream.
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
* @param in_buff - Input buffer to be used if the stream is an input type.
* @param out_buff - Output buffer to be used if the stream is an output type.
* @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
*/
static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
[[maybe_unused]] const void* in_buff, void* out_buff,
long num_frames_) {
auto* impl = static_cast<CubebSinkStream*>(user_data);
if (!impl) {
return -1;
}
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t num_frames{static_cast<size_t>(num_frames_)};
if (impl->type == StreamType::In) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
num_frames * frame_size};
impl->ProcessAudioIn(input_buffer, num_frames);
} else {
std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
}
return num_frames_;
}
/**
* Cubeb callback for if a device state changes. Unused currently.
*
* @param stream - Cubeb-specific data about the stream.
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
* @param state - New state of the device.
*/
static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
/// Main Cubeb context
cubeb* ctx{};
/// Cubeb stream backend
cubeb_stream* stream_backend{};
};
CubebSink::CubebSink(std::string_view target_device_name) {
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
#ifdef _WIN32
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
if (cubeb_init(&ctx, "Eden", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return;
}
if (target_device_name != auto_device_name && !target_device_name.empty()) {
cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
const auto collection_end{collection.device + collection.count};
const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
return info.friendly_name != nullptr &&
target_device_name == std::string(info.friendly_name);
})};
if (device != collection_end) {
output_device = device->devid;
}
cubeb_device_collection_destroy(ctx, &collection);
}
}
cubeb_get_max_channel_count(ctx, &device_channels);
device_channels = device_channels >= 6U ? 6U : 2U;
}
CubebSink::~CubebSink() {
if (!ctx) {
return;
}
for (auto& sink_stream : sink_streams) {
sink_stream.reset();
}
cubeb_destroy(ctx);
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
}
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
const std::string& name, StreamType type) {
system_channels = system_channels_;
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
return stream.get();
}
void CubebSink::CloseStream(SinkStream* stream) {
for (size_t i = 0; i < sink_streams.size(); i++) {
if (sink_streams[i].get() == stream) {
sink_streams[i].reset();
sink_streams.erase(sink_streams.begin() + i);
break;
}
}
}
void CubebSink::CloseStreams() {
sink_streams.clear();
}
f32 CubebSink::GetDeviceVolume() const {
if (sink_streams.empty()) {
return 1.0f;
}
return sink_streams[0]->GetDeviceVolume();
}
void CubebSink::SetDeviceVolume(f32 volume) {
for (auto& stream : sink_streams) {
stream->SetDeviceVolume(volume);
}
}
void CubebSink::SetSystemVolume(f32 volume) {
for (auto& stream : sink_streams) {
stream->SetSystemVolume(volume);
}
}
std::vector<std::string> ListCubebSinkDevices(bool capture) {
std::vector<std::string> device_list;
cubeb* ctx;
#ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
if (cubeb_init(&ctx, "Eden Device Enumerator", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return {};
}
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
for (std::size_t i = 0; i < collection.count; i++) {
const cubeb_device_info& device = collection.device[i];
if (device.friendly_name && device.friendly_name[0] != '\0' &&
device.state == CUBEB_DEVICE_STATE_ENABLED) {
device_list.emplace_back(device.friendly_name);
}
}
cubeb_device_collection_destroy(ctx, &collection);
}
cubeb_destroy(ctx);
return device_list;
}
/* REVERSION TO 3833 - function GetCubebLatency REINTRODUCED FROM 3833 - DIABLO 3 FIX */
u32 GetCubebLatency() {
cubeb* ctx;
#ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
// Init cubeb
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
// Return a large latency so we choose SDL instead.
return 10000u;
}
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
// Get min latency
cubeb_stream_params params{};
params.rate = TargetSampleRate;
params.channels = 2;
params.format = CUBEB_SAMPLE_S16LE;
params.prefs = CUBEB_STREAM_PREF_NONE;
params.layout = CUBEB_LAYOUT_STEREO;
u32 latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
if (latency_error != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
latency = TargetSampleCount * 2;
}
latency = (std::max)(latency, TargetSampleCount * 2);
cubeb_destroy(ctx);
return latency;
}
// REVERTED back to 3833 - Below namespace section and function IsCubebSuitable() removed, reverting to GetCubebLatency() above. - DIABLO 3 FIX
/*
namespace {
static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
return TargetSampleCount;
}
static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
} // namespace
bool IsCubebSuitable() {
#if !defined(HAVE_CUBEB)
return false;
#else
cubeb* ctx{nullptr};
#ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
// Init cubeb
if (cubeb_init(&ctx, "Eden Latency Getter", nullptr) != CUBEB_OK) {
LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
return false;
}
SCOPE_EXIT {
cubeb_destroy(ctx);
};
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
// Get min latency
cubeb_stream_params params{};
params.rate = TargetSampleRate;
params.channels = 2;
params.format = CUBEB_SAMPLE_S16LE;
params.prefs = CUBEB_STREAM_PREF_NONE;
params.layout = CUBEB_LAYOUT_STEREO;
u32 latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
if (latency_error != CUBEB_OK) {
LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
return false;
}
latency = (std::max)(latency, TargetSampleCount * 2);
// Test opening a device with standard parameters
cubeb_devid output_device{0};
cubeb_devid input_device{0};
std::string name{"Eden test"};
cubeb_stream* stream{nullptr};
if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
return false;
}
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
return true;
#endif
}
*/
} // namespace AudioCore::Sink
-118
View File
@@ -1,118 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#include <cubeb/cubeb.h>
#include "audio_core/sink/sink.h"
namespace Core {
class System;
}
namespace AudioCore::Sink {
class SinkStream;
/**
* Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to
* hardware. Used by Audio Render, Audio In and Audio Out.
*/
class CubebSink final : public Sink {
public:
explicit CubebSink(std::string_view device_id);
~CubebSink() override;
/**
* Create a new sink stream.
*
* @param system - Core system.
* @param system_channels - Number of channels the audio system expects.
* May differ from the device's channel count.
* @param name - Name of this stream.
* @param type - Type of this stream, render/in/out.
*
* @return A pointer to the created SinkStream
*/
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string& name, StreamType type) override;
/**
* Close a given stream.
*
* @param stream - The stream to close.
*/
void CloseStream(SinkStream* stream) override;
/**
* Close all streams.
*/
void CloseStreams() override;
/**
* Get the device volume. Set from calls to the IAudioDevice service.
*
* @return Volume of the device.
*/
f32 GetDeviceVolume() const override;
/**
* Set the device volume. Set from calls to the IAudioDevice service.
*
* @param volume - New volume of the device.
*/
void SetDeviceVolume(f32 volume) override;
/**
* Set the system volume. Comes from the audio system using this stream.
*
* @param volume - New volume of the system.
*/
void SetSystemVolume(f32 volume) override;
private:
/// Backend Cubeb context
cubeb* ctx{};
/// Cubeb id of the actual hardware output device
cubeb_devid output_device{};
/// Cubeb id of the actual hardware input device
cubeb_devid input_device{};
/// Vector of streams managed by this sink
std::vector<SinkStreamPtr> sink_streams{};
#ifdef _WIN32
/// Cubeb required COM to be initialized multi-threaded on Windows
u32 com_init_result = 0;
#endif
};
/**
* Get a list of connected devices from Cubeb.
*
* @param capture - Return input (capture) devices if true, otherwise output devices.
*/
std::vector<std::string> ListCubebSinkDevices(bool capture);
// REVERSION - function GetCubebLatency() reintroduced from EA-3833 - DIABLO 3 FIX
/**
* Get the reported latency for this sink.
*
* @return Minimum latency for this sink.
*/
u32 GetCubebLatency();
/**
* Check if this backend is suitable for use.
* Checks if enabled, its latency, whether it opens successfully, etc.
*
* @return True is this backend is suitable, false otherwise.
*/
// bool IsCubebSuitable(); // REVERTED BACK TO GetCubebLatency() FROM 3833
} // namespace AudioCore::Sink
+60 -138
View File
@@ -25,9 +25,10 @@ namespace {
// See https://github.com/PCSX2/pcsx2/pull/12312
// "SDL and cubeb backends previously resulted in different names for the output which
// caused them be identified as different applications by the OS."
//
// Keep in sync with cubeb_sink.cpp name.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_STREAM_ROLE", "Game");
// We do our own processing, so just have SDL copy our audio
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_RAW_STREAM", "1");
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return false;
@@ -82,23 +83,18 @@ public:
SDL_AudioSpec spec{};
spec.freq = TargetSampleRate;
spec.channels = static_cast<u8>(device_channels);
spec.format = SDL_AUDIO_S16;
spec.channels = u8(device_channels);
spec.format = SDL_AUDIO_S16LE;
std::string device_name{output_device};
bool capture{false};
if (type == StreamType::In) {
device_name = input_device;
capture = true;
}
auto const is_capture = (type == StreamType::In);
std::string device_name = is_capture ? input_device : output_device;
const SDL_AudioDeviceID audio_device = device_name.empty()
? (is_capture
? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
: FindAudioDeviceByName(device_name, is_capture);
const SDL_AudioDeviceID audio_device =
device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
: FindAudioDeviceByName(device_name, capture);
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback,
this);
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback, this);
if (stream == nullptr) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
@@ -107,13 +103,12 @@ public:
SDL_AudioSpec stream_in{};
SDL_AudioSpec stream_out{};
static_cast<void>(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
void(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
LOG_INFO(Service_Audio,
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
" format {}",
static_cast<const void*>(stream), stream_out.freq, stream_out.channels,
system_channels, static_cast<int>(stream_out.format));
system_channels, int(stream_out.format));
}
/**
@@ -128,14 +123,12 @@ public:
* Finalize the sink stream.
*/
void Finalize() override {
if (stream == nullptr) {
return;
if (stream != nullptr) {
Stop();
SDL_ClearAudioStream(stream);
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
Stop();
SDL_ClearAudioStream(stream);
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
/**
@@ -148,9 +141,8 @@ public:
if (stream == nullptr || !paused) {
return;
}
paused = false;
static_cast<void>(SDL_ResumeAudioStreamDevice(stream));
void(SDL_ResumeAudioStreamDevice(stream));
}
/**
@@ -161,7 +153,7 @@ public:
return;
}
SignalPause();
static_cast<void>(SDL_PauseAudioStreamDevice(stream));
void(SDL_PauseAudioStreamDevice(stream));
}
private:
@@ -173,51 +165,34 @@ private:
* @param stream - Buffer of samples to be filled or read.
* @param len - Length of the stream in bytes.
*/
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
int total_amount) {
auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) {
return;
}
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
if (impl->type == StreamType::In) {
const int bytes_available = SDL_GetAudioStreamAvailable(stream);
if (bytes_available <= 0) {
return;
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) {
if (auto* impl = static_cast<SDLSinkStream*>(userdata); impl) {
auto const num_channels = impl->GetDeviceChannels();
auto const frame_size = num_channels;
if (impl->type == StreamType::In) {
auto const bytes_available = SDL_GetAudioStreamAvailable(stream);
if (bytes_available > 0) {
std::vector<s16> input(bytes_available / int(sizeof(s16)));
auto const bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
if (bytes_read > 0) {
auto const num_frames = std::size_t(bytes_read) / sizeof(s16) / frame_size;
std::span<const s16> input_buffer{input.data(), std::size_t(bytes_read) / sizeof(s16)};
impl->ProcessAudioIn(input_buffer, num_frames);
}
}
} else if (additional_amount > 0 || total_amount > 0) {
auto const bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
std::vector<s16> output(bytes_requested / int(sizeof(s16)));
auto const num_frames = std::size_t(bytes_requested) / sizeof(s16) / frame_size;
std::span<s16> output_buffer{output.data(), output.size()};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
void(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
}
std::vector<s16> input(bytes_available / static_cast<int>(sizeof(s16)));
const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
if (bytes_read <= 0) {
return;
}
const std::size_t num_frames =
static_cast<std::size_t>(bytes_read) / sizeof(s16) / frame_size;
std::span<const s16> input_buffer{input.data(),
static_cast<std::size_t>(bytes_read) / sizeof(s16)};
impl->ProcessAudioIn(input_buffer, num_frames);
} else {
if (additional_amount <= 0 && total_amount <= 0) {
return;
}
const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
std::vector<s16> output(bytes_requested / static_cast<int>(sizeof(s16)));
const std::size_t num_frames =
static_cast<std::size_t>(bytes_requested) / sizeof(s16) / frame_size;
std::span<s16> output_buffer{output.data(), output.size()};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
static_cast<void>(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
}
}
/// SDL stream attached to an opened input/output device
SDL_AudioStream* stream{};
SDL_AudioStream* stream = nullptr;
};
SDLSink::SDLSink(std::string_view target_device_name) {
@@ -233,11 +208,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
SDLSink::~SDLSink() = default;
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
const std::string&, StreamType type) {
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) {
system_channels = system_channels_;
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
device_channels, system_channels, output_device, input_device, type, system));
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(device_channels, system_channels, output_device, input_device, type, system));
return stream.get();
}
@@ -256,11 +229,7 @@ void SDLSink::CloseStreams() {
}
f32 SDLSink::GetDeviceVolume() const {
if (sink_streams.empty()) {
return 1.0f;
}
return sink_streams[0]->GetDeviceVolume();
return sink_streams.empty() ? 1.0f : sink_streams[0]->GetDeviceVolume();
}
void SDLSink::SetDeviceVolume(f32 volume) {
@@ -275,68 +244,21 @@ void SDLSink::SetSystemVolume(f32 volume) {
}
}
std::vector<std::string> ListSDLSinkDevices(bool capture) {
if (!InitializeAudio())
return {}; //no devices
std::vector<std::string> device_list;
int device_count = 0;
SDL_AudioDeviceID* devices =
capture ? SDL_GetAudioRecordingDevices(&device_count)
: SDL_GetAudioPlaybackDevices(&device_count);
if (devices == nullptr) {
return device_list;
}
for (int i = 0; i < device_count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(devices[i])) {
device_list.emplace_back(name);
std::vector<std::string> ListSDLSinkDevices(bool is_capture) {
std::vector<std::string> device_list{};
if (InitializeAudio()) {
int device_count = 0;
SDL_AudioDeviceID* devices = is_capture ? SDL_GetAudioRecordingDevices(&device_count) : SDL_GetAudioPlaybackDevices(&device_count);
if (devices != nullptr) {
for (int i = 0; i < device_count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(devices[i]); name) {
device_list.emplace_back(name);
}
}
SDL_free(devices);
}
}
SDL_free(devices);
return device_list;
}
/* REVERSION to 3833 - function GetSDLLatency() REINTRODUCED FROM 3833 - DIABLO 3 FIX */
u32 GetSDLLatency() {
return TargetSampleCount * 2;
}
// REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
/*
bool IsSDLSuitable() {
#if !defined(HAVE_SDL3)
return false;
#else
// Check SDL can init
if (!InitializeAudio()!
return false;
// We can set any latency frequency we want with SDL, so no need to check that.
// Check we can open a device with standard parameters
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = 2u;
spec.format = AUDIO_S16SYS;
spec.samples = TargetSampleCount * 2;
spec.callback = nullptr;
spec.userdata = nullptr;
SDL_AudioSpec obtained;
auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
if (device == 0) {
LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
SDL_CloseAudioDevice(device);
return true;
#endif
}
*/
} // namespace AudioCore::Sink
-16
View File
@@ -90,20 +90,4 @@ private:
*/
std::vector<std::string> ListSDLSinkDevices(bool capture);
// REVERSION - function GetSDLLatency() reintroduced from EA-3833 - DIABLO 3 FIX
/**
* Get the reported latency for this sink.
*
* @return Minimum latency for this sink.
*/
u32 GetSDLLatency();
/** REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
* Check if this backend is suitable for use.
* Checks if enabled, its latency, whether it opens successfully, etc.
*
* @return True is this backend is suitable, false otherwise.
*/
//bool IsSDLSuitable(); // REVERTED for GetSDLLatency() from EA-3833
} // namespace AudioCore::Sink
+7 -62
View File
@@ -10,9 +10,6 @@
#include <vector>
#include "audio_core/sink/sink_details.h"
#ifdef HAVE_CUBEB
#include "audio_core/sink/cubeb_sink.h"
#endif
#ifdef HAVE_SDL3
#include "audio_core/sink/sdl3_sink.h"
#endif
@@ -34,24 +31,10 @@ struct SinkDetails {
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
/// Method to get the latency of this backend - REINTRODUCED FROM 3833 - DIABLO 3 FIX
LatencyFn latency;
/// Check whether this backend is suitable to be used.
/// SuitableFn is_suitable; // REVERTED FOR LatencyFn latency ABOVE - DIABLO 3 FIX
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = {
#ifdef HAVE_CUBEB
SinkDetails{
Settings::AudioEngine::Cubeb,
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices,
&GetCubebLatency,
},
#endif
static constexpr SinkDetails sink_details[] = {
#ifdef HAVE_SDL3
SinkDetails{
Settings::AudioEngine::Sdl3,
@@ -59,7 +42,6 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<SDLSink>(device_id);
},
&ListSDLSinkDevices,
&GetSDLLatency,
},
#endif
SinkDetails{
@@ -68,69 +50,32 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<NullSink>(device_id);
},
[](bool capture) { return std::vector<std::string>{"null"}; },
[]() { return 0u; },
},
};
const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
const auto find_backend{[](Settings::AudioEngine id) {
return std::find_if(std::begin(sink_details), std::end(sink_details),
[&id](const auto& sink_detail) { return sink_detail.id == id; });
return std::ranges::find_if(std::begin(sink_details), std::end(sink_details), [&id](const auto& e) {
return e.id == id;
});
}};
auto iter = find_backend(sink_id);
if (sink_id == Settings::AudioEngine::Auto) {
// REVERTED TO 3833 BELOW - DIABLO 3 FIX
/*
// Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
// that the backend is available and suitable to use.
for (auto& details : sink_details) {
if (details.is_suitable()) {
iter = &details;
break;
}
}
*/ // END REVERTED CODE - DIABLO 3 FIX
// BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
// causes audio issues, in that case go with SDL.
#if defined(HAVE_CUBEB) && defined(HAVE_SDL3)
iter = find_backend(Settings::AudioEngine::Cubeb);
if (iter->latency() > TargetSampleCount * 3) {
if (sink_id == Settings::AudioEngine::Cubeb || sink_id == Settings::AudioEngine::Auto) {
iter = find_backend(Settings::AudioEngine::Sdl3);
}
#else
iter = std::begin(sink_details);
#endif
// END REINTRODUCED SECTION FROM 3833 - DIABLO 3 FIX
LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
Settings::CanonicalizeEnum(iter->id));
/* BEGIN REMOVED - REVERTING BACK TO 3833, this didn't exist at all. - DIABLO 3 FIX
} else {
if (iter != std::end(sink_details) && !iter->is_suitable()) {
LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
Settings::CanonicalizeEnum(iter->id));
iter = find_backend(Settings::AudioEngine::Null);
} */ // END REMOVED REVERT - DIABLO 3 FIX
LOG_INFO(Service_Audio, "Auto-selecting the {} backend", Settings::CanonicalizeEnum(iter->id));
}
if (iter == std::end(sink_details)) {
LOG_ERROR(Audio, "Invalid sink_id {}", Settings::CanonicalizeEnum(sink_id));
iter = find_backend(Settings::AudioEngine::Null);
}
return *iter;
}
} // Anonymous namespace
std::vector<Settings::AudioEngine> GetSinkIDs() {
std::vector<Settings::AudioEngine> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; });
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), [](const auto& sink) { return sink.id; });
return sink_ids;
}
+2 -2
View File
@@ -108,8 +108,6 @@ add_library(
settings_setting.h
slot_vector.h
socket_types.h
sparse_large_vector.cpp
sparse_large_vector.h
spin_lock.h
stb.cpp
stb.h
@@ -139,6 +137,8 @@ add_library(
uuid.cpp
uuid.h
vector_math.h
virtual_buffer.cpp
virtual_buffer.h
zstd_compression.cpp
zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
+1
View File
@@ -9,6 +9,7 @@
#include "common/assert.h"
#include "common/fiber.h"
#include "common/virtual_buffer.h"
#include <boost/context/detail/fcontext.hpp>
+14 -41
View File
@@ -117,6 +117,7 @@ template <typename T>
static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) {
if (!dll.GetSymbol(name, &pfn)) {
LOG_CRITICAL(HW_Memory, "Failed to load {}", name);
throw std::bad_alloc{};
}
}
@@ -178,14 +179,6 @@ public:
Release();
}
void* Allocate(size_t size) {
auto* ptr = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (ptr == nullptr) {
LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, error {}", size, GetLastError());
}
return ptr;
}
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
std::unique_lock lock{placeholder_mutex};
if (!IsNiechePlaceholder(virtual_offset, length)) {
@@ -406,10 +399,6 @@ private:
// For managarm: see https://github.com/managarm/managarm/issues/1370
#else // ^^^ Windows ^^^ vvv POSIX vvv
#ifndef MAP_NOCORE
#define MAP_NOCORE 0
#endif
#ifdef ARCHITECTURE_arm64
static void* ChooseVirtualBase(size_t virtual_size) {
@@ -434,7 +423,7 @@ static void* ChooseVirtualBase(size_t virtual_size) {
// Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
void* map_pointer =
mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0);
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
// If we successfully mapped, we're done.
if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
@@ -454,11 +443,11 @@ static void* ChooseVirtualBase(size_t virtual_size) {
static void* ChooseVirtualBase(size_t virtual_size) {
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER | MAP_NOCORE, -1, 0);
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
if (virtual_base != MAP_FAILED)
return virtual_base;
#endif
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0);
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}
#endif
@@ -552,13 +541,13 @@ public:
}
if (use_anon) {
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NOCORE, -1, 0));
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) {
fd = -1;
close(fd);
}
} else {
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCORE, fd, 0));
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
}
if (backing_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -582,14 +571,6 @@ public:
Release();
}
void* Allocate(size_t size) {
auto* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (ptr == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, {}", size, strerror(errno));
}
return ptr;
}
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
// Intersect the range with our address space.
AdjustMap(&virtual_offset, &length);
@@ -710,10 +691,12 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
{
#if defined(__OPENORBIS__) || defined(__managarm__)
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
fallback_buffer.emplace(backing_size);
backing_base = fallback_buffer->data();
virtual_base = nullptr;
#else
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
if (impl->Init()) {
backing_base = impl->backing_base;
@@ -724,26 +707,16 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
virtual_base_offset = virtual_base - impl->virtual_base;
}
} else {
LOG_WARNING(HW_Memory, "Platform can support fastmem, but can't create it");
fallback_buffer = true;
backing_base = static_cast<u8*>(impl->Allocate(backing_size));
virtual_base = nullptr;
impl.reset();
LOG_WARNING(HW_Memory, "Platform can support fastmem, but can't create it");
fallback_buffer.emplace(backing_size);
backing_base = fallback_buffer->data();
virtual_base = nullptr;
}
#endif
}
HostMemory::~HostMemory() {
#ifdef _WIN32
if (fallback_buffer) {
VirtualFree(backing_base, backing_size, MEM_RELEASE);
}
#else
if (fallback_buffer) {
munmap(backing_base, backing_size);
}
#endif
}
HostMemory::~HostMemory() = default;
HostMemory::HostMemory(HostMemory&&) noexcept = default;
+2 -1
View File
@@ -10,6 +10,7 @@
#include <optional>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
namespace Common {
@@ -85,7 +86,7 @@ private:
u8* virtual_base{};
size_t virtual_base_offset{};
// Windows requires it for kernels whom lack proper support for some functions!
bool fallback_buffer{false};
std::optional<Common::VirtualBuffer<u8>> fallback_buffer;
};
} // namespace Common
+33 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -13,11 +13,39 @@ PageTable::PageTable() = default;
PageTable::~PageTable() noexcept = default;
void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_bits) {
auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_bits);
entries.ResizeAndClear(num_page_table_entries);
bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
Common::ProcessAddress address) const {
out_context->next_offset = GetInteger(address);
out_context->next_page = address / page_size;
return this->ContinueTraversal(out_entry, out_context);
}
bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
// Setup invalid defaults.
out_entry->phys_addr = 0;
out_entry->block_size = page_size;
// Validate that we can read the actual entry.
if (auto const page = context->next_page; page < entries.size()) {
// Validate that the entry is mapped.
if (auto const paddr = entries[page].addr; paddr != 0) {
// Populate the results.
out_entry->phys_addr = paddr + context->next_offset;
context->next_page += 1;
context->next_offset += page_size;
return true;
}
}
context->next_page += 1;
context->next_offset += page_size;
return false;
}
void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits) {
auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_size_in_bits);
entries.resize(num_page_table_entries);
current_address_space_width_in_bits = address_space_width_in_bits;
current_page_bits = page_bits;
page_size = 1ULL << page_size_in_bits;
}
} // namespace Common
+56 -64
View File
@@ -9,22 +9,22 @@
#include <atomic>
#include "common/common_types.h"
#include "common/sparse_large_vector.h"
#include "common/typed_address.h"
#include "common/virtual_buffer.h"
namespace Common {
enum class PageType : u8 {
/// Page is unmapped and should cause an access error.
Unmapped = 0b00,
Unmapped,
/// Page is mapped to regular memory. This is the only type you can get pointers to.
Memory = 0b01,
Memory,
/// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use
/// the callbacks.
DebugMemory = 0b10,
DebugMemory,
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
/// invalidation
RasterizerCachedMemory = 0b11,
RasterizerCachedMemory,
};
/**
@@ -42,86 +42,57 @@ struct PageTable {
u64 next_offset{};
};
/// Masks out bits reserved for attribute tagging.
static constexpr u64 ATTRIBUTE_MASK = ((1ULL << 44) - 1) << 12;
/// Specifies sign bit for page table entries.
static constexpr u64 SIGN_BIT = 45 + 12; // 44 bits of data + page offset
/// Number of bits reserved for attribute tagging.
/// This can be at most the guaranteed alignment of the pointers in the page table.
static constexpr int ATTRIBUTE_BITS = 2;
/**
* Atomic tuple of host pointer, page type, and block id.
* This uses the lower bits of a given pointer to store the attributes.
* Pair of host pointer and page type attribute.
* This uses the lower bits of a given pointer to store the attribute tag.
* Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method
* call. In other words, they are guaranteed to be synchronized at all times.
*/
class PageEntryData {
class PageInfo {
public:
struct Data {
Data(bool marked_, PageType type_, u16 block_, u64 page_)
: marked(static_cast<u64>(marked_) & 0b1)
, type(static_cast<u64>(type_) & ((1ULL << 2) - 1))
, block(static_cast<u64>(block_) & ((1ULL << 9) - 1))
, page((page_ >> 12) & ((1ULL << 45) - 1))
, block2((static_cast<u64>(block_) >> 9) & ((1ULL << 7) - 1)) {}
u64 marked : 1;
u64 type : 2;
u64 block : 9;
u64 page : 45; // 44 bits of actual data (64 - page offset (12) - reserved (8)) + a sign bit
u64 block2 : 7;
};
[[nodiscard]] Data Raw() const noexcept {
return std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed));
}
/// Returns the page pointer
[[nodiscard]] uintptr_t Pointer(bool ignored_marked = false) const noexcept {
return ExtractPointer(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)), ignored_marked);
[[nodiscard]] uintptr_t Pointer() const noexcept {
return ExtractPointer(raw.load(std::memory_order_relaxed));
}
/// Returns the page type attribute
[[nodiscard]] PageType Type() const noexcept {
return static_cast<PageType>(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)).type);
}
/// Returns the block identifier.
[[nodiscard]] u16 Block() const noexcept {
return ExtractBlock(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)));
return ExtractType(raw.load(std::memory_order_relaxed));
}
/// Returns the page pointer and attribute pair, extracted from the same atomic read
[[nodiscard]] std::tuple<uintptr_t, PageType, u16> PointerTypeBlock(bool ignore_marked = false) const noexcept {
const auto non_atomic_raw = std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed));
return {ExtractPointer(non_atomic_raw, ignore_marked), static_cast<PageType>(non_atomic_raw.type), ExtractBlock(non_atomic_raw)};
[[nodiscard]] std::pair<uintptr_t, PageType> PointerType() const noexcept {
const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed);
return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)};
}
/// Write page info atomically
constexpr void Store(bool marked, PageType type, u16 block, uintptr_t pointer) noexcept {
data_raw.store(std::bit_cast<u64>(Data{marked, type, block, pointer}));
/// Returns the raw representation of the page information.
/// Use ExtractPointer and ExtractType to unpack the value.
[[nodiscard]] uintptr_t Raw() const noexcept {
return raw.load(std::memory_order_relaxed);
}
constexpr void MarkRasterizerCached() noexcept {
data_raw.fetch_or(0b111);
}
constexpr void MarkDebug(u64 ptr, u16 block) noexcept {
Store(true, PageType::DebugMemory, block, ptr);
/// Write a page pointer and type pair atomically
void Store(uintptr_t pointer, PageType type) noexcept {
raw.store(pointer | uintptr_t(type));
}
/// Unpack a pointer from a page info raw representation
[[nodiscard]] static uintptr_t ExtractPointer(Data raw, bool ignore_marked = false) noexcept {
return raw.marked && !ignore_marked ? 0
// shift raw.page's fake sign bit to the actual sign bit, then sign extend
: ((s64)(raw.page << (64 - 44))) >> (64 - 44 - 12);
[[nodiscard]] static uintptr_t ExtractPointer(uintptr_t raw) noexcept {
return raw & (~uintptr_t{0} << ATTRIBUTE_BITS);
}
[[nodiscard]] static u16 ExtractBlock(Data raw) noexcept {
return static_cast<u16>(raw.block | (raw.block2 << 9));
/// Unpack a page type from a page info raw representation
[[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept {
return static_cast<PageType>(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1));
}
private:
std::atomic<u64> data_raw;
static_assert(sizeof(Data) == sizeof(std::atomic<u64>));
std::atomic<uintptr_t> raw;
};
PageTable();
@@ -129,8 +100,13 @@ struct PageTable {
PageTable(const PageTable&) = delete;
PageTable& operator=(const PageTable&) = delete;
PageTable(PageTable&&) noexcept = delete;
PageTable& operator=(PageTable&&) noexcept = delete;
PageTable(PageTable&&) noexcept = default;
PageTable& operator=(PageTable&&) noexcept = default;
bool BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
Common::ProcessAddress address) const;
bool ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const;
/**
* Resizes the page table to be able to accommodate enough pages within
@@ -145,14 +121,30 @@ struct PageTable {
return current_address_space_width_in_bits;
}
bool GetPhysicalAddress(Common::PhysicalAddress* out_phys_addr,
Common::ProcessAddress virt_addr) const {
if (virt_addr > (1ULL << this->GetAddressSpaceBits())) {
return false;
}
*out_phys_addr = entries[virt_addr / page_size].addr + GetInteger(virt_addr);
return true;
}
/// Vector of memory pointers backing each page. An entry can only be non-null if the
/// corresponding attribute element is of type `Memory`.
SparseLargeVector<PageEntryData> entries;
static_assert(sizeof(PageEntryData) == 8);
struct PageEntryData {
PageInfo ptr;
u64 block;
u64 addr;
u64 padding;
};
VirtualBuffer<PageEntryData> entries;
static_assert(sizeof(PageEntryData) == 32);
u8* fastmem_arena{};
std::size_t current_address_space_width_in_bits{};
std::size_t current_page_bits{};
std::size_t page_size{};
};
} // namespace Common
-143
View File
@@ -1,143 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* virtual_buffer.cpp */
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include <windows.h>
#include <mutex>
#else
#include <sys/mman.h>
#endif
#include "common/alignment.h"
#include "common/assert.h"
#include "common/sparse_large_vector.h"
namespace Common {
#ifdef _WIN32
static std::vector<std::pair<u64, u64>> vector_regions {};
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
DWORD code = info->ExceptionRecord->ExceptionCode;
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionAddress);
if (code != EXCEPTION_ACCESS_VIOLATION) {
// Not our problem
return EXCEPTION_CONTINUE_SEARCH;
}
u64 addr = 0, addr2 = 0;
for (auto region: vector_regions) {
auto addr_shifted = exception_addr >> HostPageBits;
if (region.first <= addr_shifted && addr_shifted <= region.second) {
addr = addr_shifted;
}
// Page-boundary accesses
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) {
addr2 = addr_;
}
if (addr != 0 || addr2 != 0) {
break;
}
}
if (addr == 0 && addr2 == 0) {
// Not our problem
return EXCEPTION_CONTINUE_SEARCH;
}
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr);
// Commit this region
if (addr != 0) {
if (!CommitVectorPage(addr << HostPageBits, false)) {
return EXCEPTION_CONTINUE_SEARCH;
}
}
// Commit next region if needed
if (addr2 != 0) {
if (!CommitVectorPage(addr2 << HostPageBits, false)) {
return EXCEPTION_CONTINUE_SEARCH;
}
}
return EXCEPTION_CONTINUE_EXECUTION;
}
bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
MEMORY_BASIC_INFORMATION info {};
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
if (res == 0) {
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
} else if (info.State != MEM_RESERVE) {
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped or is already committed (state {:#x})", addr, info.State);
return false;
}
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
void* res2 = VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm);
if (res2 == nullptr) {
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
return false;
}
return true;
}
#endif
#ifndef MAP_NOCORE
#define MAP_NOCORE 0
#endif
void* AllocateMemoryPages(std::size_t size) noexcept {
if (auto page = HostPageSize; size % page != 0) {
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
size = AlignUp(size, page);
}
#ifdef _WIN32
// We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
if (base != nullptr) {
vector_regions.emplace_back(reinterpret_cast<u64>(base), reinterpret_cast<u64>(base) + size);
static std::once_flag flag;
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
} else {
// Try committing everything instead??
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
}
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
#else
void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0);
if (base == MAP_FAILED)
base = nullptr;
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno));
#endif
return base;
}
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (auto page = HostPageSize; size % page != 0) {
size = AlignUp(size, page);
}
if (!base)
return;
#ifdef _WIN32
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
#else
ASSERT(munmap(base, size) == 0);
#endif
}
} // namespace Common
-194
View File
@@ -1,194 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* virtual_buffer.h */
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <bit>
#include <utility>
#include <vector>
#ifndef _WIN32
#include <unistd.h>
#include <sys/mman.h>
#endif
#include "common/alignment.h"
#include "common/assert.h"
namespace Common {
#ifdef _WIN32
constexpr u64 HostPageSize = 0x1000;
constexpr u64 HostPageBits = 12;
constexpr u64 HostPageMask = ~(HostPageSize - 1);
bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
#else
const u64 HostPageSize = sysconf(_SC_PAGESIZE);
const u64 HostPageBits = std::countr_zero(HostPageSize);
const u64 HostPageMask = ~(HostPageSize - 1);
#endif
void* AllocateMemoryPages(std::size_t size) noexcept;
void FreeMemoryPages(void* base, std::size_t size) noexcept;
/// A large page-aligned buffer that has optimized memory usage for zero-writes.
template <typename T>
requires std::is_trivially_copyable_v<T>
class SparseLargeVector final {
public:
constexpr SparseLargeVector() = default;
explicit SparseLargeVector(std::size_t count) noexcept
: alloc_size{count * sizeof(T)}
{
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
// each item in vector holds information for 64 pages
auto denom = HostPageSize * 64;
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
}
~SparseLargeVector() noexcept {
FreeMemoryPages(base_ptr, alloc_size);
}
SparseLargeVector(const SparseLargeVector&) = delete;
SparseLargeVector& operator=(const SparseLargeVector&) = delete;
SparseLargeVector(SparseLargeVector&& other) = delete;
SparseLargeVector& operator=(SparseLargeVector&& other) = delete;
void ResizeAndClear(std::size_t count) noexcept {
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
FreeMemoryPages(base_ptr, alloc_size);
alloc_size = new_size;
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
auto denom = HostPageSize * 64;
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
}
}
/// Returns a reference to the value of the requested index and allocates memory if needed.
T& GetAndFault(std::size_t index) noexcept {
if (index > alloc_size / sizeof(T)) {
UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index);
}
if (!IsCommittedPage(index)) {
CommitPage(index);
}
return base_ptr[index];
}
/// Returns a reference to the value of the requested index if initialized, or will otherwise return a zero-initialized object.
const T& GetOrDefault(std::size_t index) const {
#ifdef _WIN32
if (!IsCommittedPage(index)) {
return *reinterpret_cast<const T*>(&default_val);
}
#endif
// On non-Windows, OS page table should optimize this by pointing to a zero page if unallocated.
return base_ptr[index];
}
void Set(std::size_t index, const T& value) noexcept {
if (index > alloc_size / sizeof(T)) {
LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index);
return;
}
if (!IsCommittedPage(index))
CommitPage(index);
base_ptr[index] = value;
}
void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
u64 base = reinterpret_cast<u64>(&base_ptr[start]);
const u64 end = reinterpret_cast<u64>(&base_ptr[end_]);
const u64 end_page = AlignUp(base, HostPageSize);
const u64 first_size = (std::min)(end_page, end) - base;
if (IsCommittedPage(start / sizeof(T))) {
std::memset(reinterpret_cast<void*>(base), 0, first_size);
}
if (end <= end_page)
return;
base = end_page;
for (u64 page = base; page < end; page += HostPageSize) {
if (!IsCommittedPage((page - reinterpret_cast<u64>(base_ptr)) / sizeof(T))) {
continue;
}
std::memset(reinterpret_cast<void*>(page), 0, (std::min)( HostPageSize, end - page));
}
}
constexpr void CommitRegion(size_t index, size_t end_) {
const u64 base = static_cast<u64>(index) * sizeof(T);
const u64 end = static_cast<u64>(end_) * sizeof(T);
for (u64 page = AlignDown(base, HostPageSize); page < end; page += HostPageSize) {
if (!IsCommittedPage(page / sizeof(T))) {
CommitPage(page / sizeof(T));
}
}
}
constexpr T& GetUnchecked(size_t index) {
return base_ptr[index];
}
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
return GetOrDefault(index);
}
[[nodiscard]] constexpr const T* data() const noexcept {
return base_ptr;
}
[[nodiscard]] constexpr std::size_t size() const noexcept {
return alloc_size / sizeof(T);
}
private:
[[nodiscard]] constexpr bool IsCommittedPage(std::size_t index) const noexcept {
if (index > alloc_size / sizeof(T)) {
LOG_CRITICAL(Common_Memory, "Out of bounds access on large vector @ {}", index);
return false;
}
auto page = (index * sizeof(T)) >> HostPageBits;
auto val = committed_pages[page >> 6].load(std::memory_order_acquire);
return (val >> (page & 63)) & 1;
}
constexpr void CommitPage(std::size_t index) noexcept {
auto page_index = (index * sizeof(T)) >> HostPageBits;
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
#if defined(_WIN32)
CommitVectorPage(page, true);
#else
mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE);
#endif
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
}
std::size_t alloc_size{};
T* base_ptr{};
std::vector<std::atomic<u64>> committed_pages{};
#ifdef _WIN32
const std::array<u8, sizeof(T)> default_val{};
#endif
};
} // namespace Common
+44
View File
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include "common/assert.h"
#include "common/virtual_buffer.h"
namespace Common {
void* AllocateMemoryPages(std::size_t size) noexcept {
#ifdef _WIN32
void* base = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (base == nullptr) {
// Probably failing to reserve is less likely than failing to commit
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
}
#else
void* base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == MAP_FAILED)
base = nullptr;
#endif
ASSERT(base);
return base;
}
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (!base)
return;
#ifdef _WIN32
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
#else
ASSERT(munmap(base, size) == 0);
#endif
}
} // namespace Common
+84
View File
@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <utility>
namespace Common {
void* AllocateMemoryPages(std::size_t size) noexcept;
void FreeMemoryPages(void* base, std::size_t size) noexcept;
template <typename T>
class VirtualBuffer final {
public:
// TODO: Uncomment this and change Common::PageTable::PageInfo to be trivially constructible
// using std::atomic_ref once libc++ has support for it
// static_assert(
// std::is_trivially_constructible_v<T>,
// "T must be trivially constructible, as non-trivial constructors will not be executed "
// "with the current allocator");
constexpr VirtualBuffer() = default;
explicit VirtualBuffer(std::size_t count) noexcept
: alloc_size{count * sizeof(T)}
{
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
~VirtualBuffer() noexcept {
FreeMemoryPages(base_ptr, alloc_size);
}
VirtualBuffer(const VirtualBuffer&) = delete;
VirtualBuffer& operator=(const VirtualBuffer&) = delete;
VirtualBuffer(VirtualBuffer&& other) noexcept
: alloc_size{std::exchange(other.alloc_size, 0)}
, base_ptr{std::exchange(other.base_ptr, nullptr)}
{}
VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
alloc_size = std::exchange(other.alloc_size, 0);
base_ptr = std::exchange(other.base_ptr, nullptr);
return *this;
}
void resize(std::size_t count) noexcept {
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
FreeMemoryPages(base_ptr, alloc_size);
alloc_size = new_size;
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
}
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
return base_ptr[index];
}
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept {
return base_ptr[index];
}
[[nodiscard]] constexpr T* data() noexcept {
return base_ptr;
}
[[nodiscard]] constexpr const T* data() const noexcept {
return base_ptr;
}
[[nodiscard]] constexpr std::size_t size() const noexcept {
return alloc_size / sizeof(T);
}
private:
std::size_t alloc_size{};
T* base_ptr{};
};
} // namespace Common
+6
View File
@@ -711,6 +711,8 @@ add_library(core STATIC
hle/service/kernel_helpers.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/client_process_monitor.cpp
hle/service/ldn/client_process_monitor.h
hle/service/ldn/lan_discovery.cpp
hle/service/ldn/lan_discovery.h
hle/service/ldn/ldn.cpp
@@ -830,6 +832,8 @@ add_library(core STATIC
hle/service/ns/system_update_control.h
hle/service/ns/system_update_interface.cpp
hle/service/ns/system_update_interface.h
hle/service/ns/vulnerability_manager_interface.cpp
hle/service/ns/vulnerability_manager_interface.h
hle/service/nvdrv/core/container.cpp
hle/service/nvdrv/core/container.h
hle/service/nvdrv/core/heap_mapper.cpp
@@ -1063,6 +1067,8 @@ add_library(core STATIC
hle/service/sockets/sockets.h
hle/service/sockets/sockets_translate.cpp
hle/service/sockets/sockets_translate.h
hle/service/spl/csrng.cpp
hle/service/spl/csrng.h
hle/service/spl/spl.cpp
hle/service/spl/spl.h
hle/service/spl/spl_module.cpp
+5 -13
View File
@@ -172,12 +172,12 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
if (page_table) {
constexpr size_t PageBits = 12;
constexpr size_t NumPageTableEntries = 1 << (32 - PageBits);
constexpr size_t PageLog2Stride = 5;
static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData));
// Dynarmic will not write to the page table, const_cast is safe here
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>(
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
config.page_table_marked_bit = 0;
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>(page_table->entries.data());
config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
config.page_table_log2_stride = PageLog2Stride;
config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
@@ -188,13 +188,6 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
config.recompile_on_exclusive_fastmem_failure = true;
if (reinterpret_cast<u64>(m_system.DeviceMemory().buffer.BackingBasePointer() +
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
// we have to manually sign extend when our actual pointer is negative.
config.page_table_sign_extension = Common::PageTable::SIGN_BIT;
}
}
// Multi-process state
@@ -427,7 +420,6 @@ void ArmDynarmic32::SignalInterrupt(Kernel::KThread* thread) {
}
void ArmDynarmic32::ClearInstructionCache() {
m_cb->last_code_addr = u64(-1);
m_jit->ClearCache();
}
+6 -13
View File
@@ -211,12 +211,13 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
// Memory
if (page_table) {
// Dynarmic will not write to the page table, const_cast is safe here
config.page_table = reinterpret_cast<void**>(
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
constexpr size_t PageLog2Stride = 5;
static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData));
config.page_table = reinterpret_cast<void**>(page_table->entries.data());
config.page_table_address_space_bits = std::uint32_t(address_space_bits);
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
config.page_table_marked_bit = 0;
config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
config.page_table_log2_stride = PageLog2Stride;
config.silently_mirror_page_table = false;
config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
@@ -230,13 +231,6 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
config.recompile_on_exclusive_fastmem_failure = true;
if (reinterpret_cast<u64>(m_system.DeviceMemory().buffer.BackingBasePointer() +
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
// we have to manually sign extend when our actual pointer is negative.
config.page_table_sign_extension = Common::PageTable::SIGN_BIT;
}
}
// Multi-process state
@@ -453,7 +447,6 @@ void ArmDynarmic64::SignalInterrupt(Kernel::KThread* thread) {
}
void ArmDynarmic64::ClearInstructionCache() {
m_cb->last_code_addr = u64(-1);
m_jit->ClearCache();
}
+2 -8
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -29,11 +26,8 @@ public:
template <typename T>
Common::PhysicalAddress GetPhysicalAddr(const T* ptr) const {
return GetPhysicalAddr(reinterpret_cast<uintptr_t>(ptr));
}
Common::PhysicalAddress GetPhysicalAddr(uintptr_t ptr) const {
return (ptr - reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
return (reinterpret_cast<uintptr_t>(ptr) -
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
DramMemoryMap::Base;
}
+5 -5
View File
@@ -18,7 +18,7 @@
#include "common/common_types.h"
#include "common/range_mutex.h"
#include "common/scratch_buffer.h"
#include "common/sparse_large_vector.h"
#include "common/virtual_buffer.h"
namespace Core {
@@ -178,8 +178,8 @@ private:
u32 continuity_tracker;
u32 compressed_physical_ptr;
};
Common::SparseLargeVector<u32> compressed_device_addr;
Common::SparseLargeVector<TrackedEntry> tracked_entries;
Common::VirtualBuffer<u32> compressed_device_addr;
Common::VirtualBuffer<TrackedEntry> tracked_entries;
// Process memory interfaces
@@ -200,8 +200,8 @@ private:
return std::make_pair(asid, address);
}
constexpr void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
tracked_entries.GetUnchecked(page_index).cpu_backing_address = address | (asid.id << asid_start_bit);
void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
tracked_entries[page_index].cpu_backing_address = address | (asid.id << asid_start_bit);
}
std::array<TranslationEntry, 4> t_slot{};
+27 -22
View File
@@ -177,6 +177,17 @@ DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memo
{
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
cached_pages = std::make_unique<CachedPages>();
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
for (size_t i = 0; i < total_virtual; i++) {
tracked_entries[i].compressed_physical_ptr = 0;
tracked_entries[i].continuity_tracker = 1;
tracked_entries[i].cpu_backing_address = 0;
}
const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS);
for (size_t i = 0; i < total_phys; i++) {
compressed_device_addr[i] = 0;
}
}
template <typename Traits>
@@ -209,28 +220,26 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
std::scoped_lock lk(mapping_guard);
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages);
for (size_t i = 0; i < num_pages; i++) {
const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE;
auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress));
if (ptr == nullptr) [[unlikely]] {
tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = 0;
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
continue;
}
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = phys_addr;
tracked_entries[start_page_d + i].compressed_physical_ptr = phys_addr;
InsertCPUBacking(start_page_d + i, new_vaddress, asid);
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
const u32 new_dev = static_cast<u32>(start_page_d + i);
if (base_dev == 0) [[likely]] {
compressed_device_addr.GetAndFault(phys_addr - 1U) = new_dev;
compressed_device_addr[phys_addr - 1U] = new_dev;
continue;
}
u32 start_id = base_dev & MULTI_MASK;
if ((base_dev >> MULTI_FLAG_BITS) == 0) {
start_id = impl->multi_dev_address.Register(base_dev);
compressed_device_addr.GetAndFault(phys_addr - 1U) = MULTI_FLAG | start_id;
compressed_device_addr[phys_addr - 1U] = MULTI_FLAG | start_id;
}
impl->multi_dev_address.Register(new_dev, start_id);
}
@@ -246,26 +255,24 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
device_inter->InvalidateRegion(address, size);
std::scoped_lock lk(mapping_guard);
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); // should already be committed, but just in case
for (size_t i = 0; i < num_pages; i++) {
auto& entry = tracked_entries.GetUnchecked(start_page_d + i);
auto phys_addr = entry.compressed_physical_ptr;
entry.compressed_physical_ptr = 0;
entry.cpu_backing_address = 0;
auto phys_addr = tracked_entries[start_page_d + i].compressed_physical_ptr;
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
tracked_entries[start_page_d + i].cpu_backing_address = 0;
if (phys_addr != 0) [[likely]] {
u32& base_dev = compressed_device_addr.GetAndFault(phys_addr - 1U);
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] {
base_dev = 0;
compressed_device_addr[phys_addr - 1] = 0;
continue;
}
const auto [more_entries, new_start] = impl->multi_dev_address.Unregister(
static_cast<u32>(start_page_d + i), base_dev & MULTI_MASK);
if (!more_entries) {
base_dev = impl->multi_dev_address.ReleaseEntry(new_start);
compressed_device_addr[phys_addr - 1] =
impl->multi_dev_address.ReleaseEntry(new_start);
continue;
}
base_dev = new_start | MULTI_FLAG;
compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG;
}
}
t_slot = {};
@@ -278,8 +285,6 @@ void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtu
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
uintptr_t last_ptr = 0;
size_t page_count = 1;
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages);
for (size_t i = num_pages; i > 0; i--) {
size_t index = i - 1;
const VAddr new_vaddress = virtual_address + index * Memory::YUZU_PAGESIZE;
@@ -291,14 +296,14 @@ void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtu
page_count = 1;
}
last_ptr = new_ptr;
tracked_entries.GetUnchecked(start_page_d + index).continuity_tracker = static_cast<u32>(page_count) - 1;
tracked_entries[start_page_d + index].continuity_tracker = static_cast<u32>(page_count);
}
}
template <typename Traits>
u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) {
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
@@ -308,7 +313,7 @@ template <typename Traits>
const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) const {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) {
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
@@ -378,7 +383,7 @@ void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto o
std::size_t page_index = addr >> Memory::YUZU_PAGEBITS;
std::size_t page_offset = addr & Memory::YUZU_PAGEMASK;
while (remaining_size) {
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker+1);
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker);
const std::size_t copy_amount = (std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
const auto current_vaddr = u64((page_index << Memory::YUZU_PAGEBITS) + page_offset);
SCOPE_EXIT{
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -124,7 +121,6 @@ size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
}
size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
ASSERT(m_data_size >= 0);
return m_data_size;
}
+5 -12
View File
@@ -120,7 +120,7 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
IPSwitchRecord r{};
for (auto it = sv.cbegin(); it < sv.cend(); ) {
for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) {
switch (it[1]) {
case 'a': r.data[r.count] = '\a'; break;
@@ -198,8 +198,6 @@ void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
LOG_WARNING(Loader, "Unknown flag {}", line);
break;
}
} else if (patches.empty()) {
LOG_WARNING(Loader, "Invalid line not in a patch {}", line);
} else {
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
offset += size_t(offset_shift);
@@ -255,30 +253,25 @@ void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
// now make a nominal preprocessed line: remove comments
char quote = '\0';
auto const sline_start = p;
auto last_char = p;
for (; p < sline.cend(); ) {
// we dont check for "//", IPS checks for '/' only...
if (std::isspace(*p)) {
++p;
} else if ((!quote && p[0] == '/')
if ((!quote && p[0] == '/')
|| (!quote && p[0] == '#')) {
break;
} else if (p[0] == '\"' || p[0] == '\'') {
quote = (p[0] == quote) ? '\0' : p[0];
++p;
last_char = p;
} else if (p + 1 < sline.cend() && p[0] == '\\') {
p += 2;
last_char = p;
} else {
++p;
last_char = p;
}
}
// now we have the preprocessed string ;)
std::string_view const pp_str(sline_start, last_char);
if (pp_str.size() > 0 && !parse_line(pp_str))
std::string_view pp_str(sline_start, p);
if (pp_str.size() > 0 && !parse_line(pp_str)) {
break;
}
}
}
}
+40 -70
View File
@@ -635,36 +635,6 @@ Result KPageTableBase::CheckMemoryState(const KMemoryInfo& info, KMemoryState st
R_SUCCEED();
}
bool KPageTableBase::BeginTraversal(const Common::PageTable &impl, TraversalEntry *out_entry, TraversalContext *out_context,
Common::ProcessAddress address) const {
out_context->next_offset = GetInteger(address);
out_context->next_page = GetInteger(address) >> PageBits;
return ContinueTraversal(impl, out_entry, out_context);
}
bool KPageTableBase::ContinueTraversal(const Common::PageTable &impl, TraversalEntry *out_entry,
TraversalContext *context) const {
// Setup invalid defaults.
out_entry->phys_addr = 0;
out_entry->block_size = PageSize;
// Validate that we can read the actual entry.
if (auto const page = context->next_page; page < impl.entries.size()) {
// Validate that the entry is mapped.
if (auto const paddr = impl.entries[page].Pointer(true); paddr != 0) {
// Populate the results and return true
out_entry->phys_addr = GetInteger(m_system.DeviceMemory().GetPhysicalAddr(paddr + context->next_offset));
context->next_page += 1;
context->next_offset += PageSize;
return true;
}
}
context->next_page += 1;
context->next_offset += PageSize;
// Otherwise return false
return false;
}
Result KPageTableBase::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr,
size_t size, KMemoryState state_mask,
KMemoryState state, KMemoryPermission perm_mask,
@@ -970,7 +940,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a
size_t tot_size = 0;
next_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), region_start);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), region_start);
next_entry.block_size =
(next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1)));
@@ -1006,7 +976,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a
break;
}
next_valid = ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
}
// Check the last entry.
@@ -1784,7 +1754,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_
// Begin traversal.
TraversalContext context;
TraversalEntry next_entry;
R_UNLESS(BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr),
R_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr),
ResultInvalidCurrentMemory);
// Prepare tracking variables.
@@ -1794,7 +1764,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_
// Iterate, adding to group as we go.
while (tot_size < size) {
R_UNLESS(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)),
R_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)),
ResultInvalidCurrentMemory);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -1858,7 +1828,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr
// Begin traversal.
TraversalContext context;
TraversalEntry next_entry;
if (!BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr)) {
if (!impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr)) {
return false;
}
@@ -1869,7 +1839,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr
// Iterate, comparing expected to actual.
while (tot_size < size) {
if (!ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context))) {
if (!impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))) {
return false;
}
@@ -1926,7 +1896,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState(
// Begin a traversal.
TraversalContext context;
TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
R_UNLESS(BeginTraversal(impl, std::addressof(cur_entry), std::addressof(context), address),
R_UNLESS(impl.BeginTraversal(std::addressof(cur_entry), std::addressof(context), address),
ResultInvalidCurrentMemory);
// Traverse until we have enough size or we aren't contiguous any more.
@@ -1935,7 +1905,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState(
for (contig_size =
cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1));
contig_size < size; contig_size += cur_entry.block_size) {
if (!ContinueTraversal(impl, std::addressof(cur_entry), std::addressof(context))) {
if (!impl.ContinueTraversal(std::addressof(cur_entry), std::addressof(context))) {
break;
}
if (cur_entry.phys_addr != phys_address + contig_size) {
@@ -2364,7 +2334,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(m_impl, std::addressof(next_entry), std::addressof(context), virt_addr);
m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Set tracking variables.
@@ -2375,7 +2345,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
while (true) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(m_impl, std::addressof(next_entry), std::addressof(context));
m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
if (!traverse_valid) {
break;
}
@@ -2597,7 +2567,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre
TraversalContext context;
TraversalEntry next_entry;
ASSERT(
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address));
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address));
// Check that the physical region matches.
R_UNLESS(next_entry.phys_addr == phys_addr, ResultInvalidMemoryRegion);
@@ -2607,7 +2577,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre
next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
checked_size < size; checked_size += next_entry.block_size) {
// Continue the traversal.
ASSERT(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)));
ASSERT(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)));
// Check that the physical region matches.
R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, ResultInvalidMemoryRegion);
@@ -3059,7 +3029,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), address);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address);
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Prepare tracking variables.
@@ -3071,7 +3041,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -3159,7 +3129,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_address);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_address);
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Prepare tracking variables.
@@ -3197,7 +3167,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -3255,7 +3225,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address);
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Prepare tracking variables.
@@ -3297,7 +3267,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -3758,7 +3728,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser(
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
ASSERT(traverse_valid);
// Prepare tracking variables.
@@ -3798,7 +3768,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser(
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -3852,7 +3822,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel(
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
ASSERT(traverse_valid);
// Prepare tracking variables.
@@ -3875,7 +3845,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel(
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -3932,7 +3902,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear(
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
ASSERT(traverse_valid);
// Prepare tracking variables.
@@ -3971,7 +3941,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear(
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -4027,7 +3997,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr);
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
ASSERT(traverse_valid);
// Prepare tracking variables.
@@ -4050,7 +4020,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si
while (tot_size < size) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
if (next_entry.phys_addr != (cur_addr + cur_size)) {
@@ -4119,10 +4089,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
bool traverse_valid;
// Begin traversal.
traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry),
traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
std::addressof(src_context), src_addr);
ASSERT(traverse_valid);
traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry),
traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
std::addressof(dst_context), dst_addr);
ASSERT(traverse_valid);
@@ -4157,7 +4127,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
if (ofs + cur_copy_size != size) {
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
// Continue the src traversal.
traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry),
traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
std::addressof(src_context));
ASSERT(traverse_valid);
@@ -4168,7 +4138,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
if (cur_dst_addr + cur_min_size ==
dst_next_entry.phys_addr + dst_next_entry.block_size) {
// Continue the dst traversal.
traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry),
traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
std::addressof(dst_context));
ASSERT(traverse_valid);
@@ -4253,10 +4223,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
bool traverse_valid;
// Begin traversal.
traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry),
traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
std::addressof(src_context), src_addr);
ASSERT(traverse_valid);
traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry),
traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
std::addressof(dst_context), dst_addr);
ASSERT(traverse_valid);
@@ -4291,7 +4261,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
if (ofs + cur_copy_size != size) {
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
// Continue the src traversal.
traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry),
traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
std::addressof(src_context));
ASSERT(traverse_valid);
@@ -4302,7 +4272,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
if (cur_dst_addr + cur_min_size ==
dst_next_entry.phys_addr + dst_next_entry.block_size) {
// Continue the dst traversal.
traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry),
traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
std::addressof(dst_context));
ASSERT(traverse_valid);
@@ -4577,7 +4547,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
// Begin traversal.
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid = BeginTraversal(src_impl, std::addressof(next_entry),
bool traverse_valid = src_impl.BeginTraversal(std::addressof(next_entry),
std::addressof(context), aligned_src_start);
ASSERT(traverse_valid);
@@ -4627,7 +4597,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
// If the block's size was one page, we may need to continue traversal.
if (cur_block_size == 0 && aligned_src_size > PageSize) {
traverse_valid =
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
cur_block_addr = next_entry.phys_addr;
@@ -4640,7 +4610,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
while (aligned_src_start + tot_block_size < mapping_src_end) {
// Continue the traversal.
traverse_valid =
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
// Process the block.
@@ -4683,7 +4653,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
if (mapped_block_end + cur_block_size < aligned_src_end &&
cur_block_size == last_block_size) {
traverse_valid =
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
ASSERT(traverse_valid);
cur_block_addr = next_entry.phys_addr;
@@ -5631,7 +5601,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si
ContiguousRangeInfo(KPageTableBase& pt, KProcessAddress address, size_t size)
: m_pt(pt), m_remaining_size(size) {
// Begin a traversal.
ASSERT(m_pt.BeginTraversal(m_pt.GetImpl(), std::addressof(m_entry),
ASSERT(m_pt.GetImpl().BeginTraversal(std::addressof(m_entry),
std::addressof(m_context), address));
// Setup tracking fields.
@@ -5662,7 +5632,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si
void DetermineContiguousBlockExtents() {
// Continue traversing until we're not contiguous, or we have enough.
while (m_cur_size < m_remaining_size) {
ASSERT(m_pt.ContinueTraversal(m_pt.GetImpl(), std::addressof(m_entry),
ASSERT(m_pt.GetImpl().ContinueTraversal(std::addressof(m_entry),
std::addressof(m_context)));
// If we're not contiguous, we're done.
+1 -12
View File
@@ -370,10 +370,6 @@ private:
size_t num_pages, size_t alignment, size_t offset,
size_t guard_pages) const;
bool BeginTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* out_context,
Common::ProcessAddress address) const;
bool ContinueTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* context) const;
Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
KMemoryState state_mask, KMemoryState state,
KMemoryPermission perm_mask, KMemoryPermission perm,
@@ -478,14 +474,7 @@ private:
// Validate pre-conditions.
ASSERT(this->IsLockedByCurrentThread());
if (virt_addr > (1ULL << m_address_space_width)) {
return false;
}
*out = m_system.DeviceMemory().GetPhysicalAddr(
this->GetImpl().entries[GetInteger(virt_addr) >> PageBits].Pointer(true) + GetInteger(virt_addr));
return true;
return this->GetImpl().GetPhysicalAddress(out, virt_addr);
}
public:
+6 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-late
@@ -2501,15 +2501,14 @@ void Call(Core::System& system, u32 imm) {
auto& process = GetCurrentProcess(kernel);
std::array<uint64_t, 8> args;
kernel.CurrentPhysicalCore().SaveSvcArguments(process, args);
//kernel.EnterSVCProfile();
LOG_TRACE(Kernel_SVC, "{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x}",
imm, GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2),
GetArg64(args, 3), GetArg64(args, 4), GetArg64(args, 5), GetArg64(args, 6));
if (process.Is64Bit()) {
imm, GetArg32(args, 0), GetArg32(args, 1), GetArg32(args, 2),
GetArg32(args, 3), GetArg32(args, 4), GetArg32(args, 5), GetArg32(args, 6));
//kernel.EnterSVCProfile();
if (process.Is64Bit())
Call64(system, imm, args);
} else {
else
Call32(system, imm, args);
}
//kernel.ExitSVCProfile();
kernel.CurrentPhysicalCore().LoadSvcArguments(process, args);
}
+5 -5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -29,14 +26,17 @@ IStorage::~IStorage() = default;
Result IStorage::Open(Out<SharedPointer<IStorageAccessor>> out_storage_accessor) {
LOG_DEBUG(Service_AM, "called");
R_UNLESS(m_impl->GetHandle() == nullptr, AM::ResultInvalidStorageType);
*out_storage_accessor = std::make_shared<IStorageAccessor>(system, m_impl);
R_SUCCEED();
}
Result IStorage::OpenTransferStorage(Out<SharedPointer<ITransferStorageAccessor>> out_transfer_storage_accessor) {
LOG_DEBUG(Service_AM, "called");
Result IStorage::OpenTransferStorage(
Out<SharedPointer<ITransferStorageAccessor>> out_transfer_storage_accessor) {
R_UNLESS(m_impl->GetHandle() != nullptr, AM::ResultInvalidStorageType);
*out_transfer_storage_accessor = std::make_shared<ITransferStorageAccessor>(system, m_impl);
R_SUCCEED();
}
-1
View File
@@ -33,7 +33,6 @@ constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
constexpr Result ResultOutOfOpusDecoders{ErrorModule::HwOpus, 385};
constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ldn/ldn_results.h"
#include "core/hle/service/ldn/client_process_monitor.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::LDN {
IClientProcessMonitor::IClientProcessMonitor(Core::System& system_)
: ServiceFramework{system_, "IClientProcessMonitor"} {
static const FunctionInfo functions[] = {
{0, D<&IClientProcessMonitor::RegisterClient>, "RegisterClient"},
};
RegisterHandlers(functions);
}
IClientProcessMonitor::~IClientProcessMonitor() = default;
Result IClientProcessMonitor::RegisterClient() {
LOG_WARNING(Service_LDN, "(STUBBED) called");
R_SUCCEED();
}
} // namespace Service::LDN
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::LDN {
class IClientProcessMonitor final
: public ServiceFramework<IClientProcessMonitor> {
public:
explicit IClientProcessMonitor(Core::System& system_);
~IClientProcessMonitor() override;
private:
Result RegisterClient();
};
} // namespace Service::LDN
+3 -21
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -7,6 +7,7 @@
#include "core/core.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/ldn/client_process_monitor.h"
#include "core/hle/service/ldn/monitor_service.h"
#include "core/hle/service/ldn/sf_monitor_service.h"
#include "core/hle/service/ldn/sf_service.h"
@@ -16,26 +17,6 @@
namespace Service::LDN {
class IClientProcessMonitor final
: public ServiceFramework<IClientProcessMonitor> {
public:
explicit IClientProcessMonitor(Core::System& system_)
: ServiceFramework{system_, "IClientProcessMonitor"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IClientProcessMonitor::RegisterClient>, "RegisterClient"},
};
// clang-format on
RegisterHandlers(functions);
}
~IClientProcessMonitor() override = default;
private:
Result RegisterClient(ClientProcessId pid) {
LOG_WARNING(Service_LDN, "(STUBBED) called");
R_SUCCEED();
}
};
class IMonitorServiceCreator final : public ServiceFramework<IMonitorServiceCreator> {
public:
explicit IMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
@@ -44,6 +25,7 @@ public:
{0, C<&IMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"}
};
// clang-format on
RegisterHandlers(functions);
}
@@ -139,7 +139,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
{406, nullptr, "GetApplicationControlProperty"},
{407, &IApplicationManagerInterface::ListApplicationTitle, "ListApplicationTitle"},
{408, &IApplicationManagerInterface::ListApplicationIcon, "ListApplicationIcon"},
{408, nullptr, "ListApplicationIcon"},
{411, nullptr, "Unknown411"}, //19.0.0+
{412, nullptr, "Unknown412"}, //19.0.0+
{413, nullptr, "Unknown413"}, //19.0.0+
@@ -894,9 +894,4 @@ void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx)
IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx);
}
void IApplicationManagerInterface::ListApplicationIcon(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IReadOnlyApplicationControlDataInterface(system).ListApplicationIcon(ctx);
}
} // namespace Service::NS
@@ -78,7 +78,6 @@ public:
void PushApplicationRecord(HLERequestContext& ctx);
void ListApplicationTitle(HLERequestContext& ctx);
void ListApplicationIcon(HLERequestContext& ctx);
private:
KernelHelpers::ServiceContext service_context;
+1 -40
View File
@@ -10,9 +10,7 @@
#include "core/hle/service/ns/query_service.h"
#include "core/hle/service/ns/service_getter_interface.h"
#include "core/hle/service/ns/system_update_interface.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ns/vulnerability_manager_interface.h"
#include "core/hle/service/server_manager.h"
namespace Service::NS {
@@ -41,43 +39,6 @@ public:
}
};
class IVulnerabilityManagerInterface final
: public ServiceFramework<IVulnerabilityManagerInterface> {
public:
explicit IVulnerabilityManagerInterface(Core::System& system_)
: ServiceFramework{system_, "ns:vm"} {
// clang-format off
static const FunctionInfo functions[] = {
{1200, D<&IVulnerabilityManagerInterface::NeedsUpdateVulnerability>, "NeedsUpdateVulnerability"},
{1201, nullptr, "UpdateSafeSystemVersionForDebug"},
{1202, nullptr, "GetSafeSystemVersion"},
{3100, D<&IVulnerabilityManagerInterface::GetSafeSystemVersionCheckInfo>, "GetSafeSystemVersionCheckInfo"},
{3101, nullptr, "RequestUpdateSafeSystemVersionCheckInfo"},
{3102, D<&IVulnerabilityManagerInterface::ResetSafeSystemVersionCheckInfo>, "ResetSafeSystemVersionCheckInfo"},
};
// clang-format on
RegisterHandlers(functions);
}
~IVulnerabilityManagerInterface() override = default;
Result NeedsUpdateVulnerability(Out<bool> out_needs_update_vulnerability) {
LOG_WARNING(Service_NS, "(STUBBED)");
*out_needs_update_vulnerability = false;
R_SUCCEED();
}
Result GetSafeSystemVersionCheckInfo(Out<std::array<u8, 0x20>> out_system_version_check_info) {
LOG_WARNING(Service_NS, "(STUBBED)");
*out_system_version_check_info = {};
R_SUCCEED();
}
Result ResetSafeSystemVersionCheckInfo() {
LOG_WARNING(Service_NS, "(STUBBED)");
R_SUCCEED();
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
@@ -10,17 +10,13 @@
#include <string>
#include "common/stb.h"
#include "common/logging.h"
#include "common/settings.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/ns/ns_results.h"
@@ -76,26 +72,24 @@ void SanitizeJPEGImageSize(std::vector<u8>& image) {
// IAsyncValue implementation for ListApplicationTitle
// https://switchbrew.org/wiki/NS_services#ListApplicationTitle
class IAsyncValue final : public ServiceFramework<IAsyncValue> {
class IAsyncValueForListApplicationTitle final : public ServiceFramework<IAsyncValueForListApplicationTitle> {
public:
explicit IAsyncValue(Core::System& system_, s32 offset, s32 size)
: ServiceFramework{system_, "IAsyncValue"}
, service_context{system_, "IAsyncValue"}
, data_offset{offset}
, data_size{size}
{
explicit IAsyncValueForListApplicationTitle(Core::System& system_, s32 offset, s32 size)
: ServiceFramework{system_, "IAsyncValue"}, service_context{system_, "IAsyncValue"},
data_offset{offset}, data_size{size} {
static const FunctionInfo functions[] = {
{0, D<&IAsyncValue::GetSize>, "GetSize"},
{1, D<&IAsyncValue::Get>, "Get"},
{2, D<&IAsyncValue::Cancel>, "Cancel"},
{3, D<&IAsyncValue::GetErrorContext>, "GetErrorContext"},
{0, &IAsyncValueForListApplicationTitle::GetSize, "GetSize"},
{1, &IAsyncValueForListApplicationTitle::Get, "Get"},
{2, &IAsyncValueForListApplicationTitle::Cancel, "Cancel"},
{3, &IAsyncValueForListApplicationTitle::GetErrorContext, "GetErrorContext"},
};
RegisterHandlers(functions);
completion_event = service_context.CreateEvent("IAsyncValue:Completion");
completion_event->GetReadableEvent().Signal(system.Kernel());
}
~IAsyncValue() override {
~IAsyncValueForListApplicationTitle() override {
service_context.CloseEvent(completion_event);
}
@@ -104,24 +98,35 @@ public:
}
private:
Result GetSize(Out<s64> out_data_size) {
void GetSize(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
*out_data_size = data_size;
R_SUCCEED();
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push<s64>(data_size);
}
Result Get(OutBuffer<BufferAttr_HipcMapAlias> out_data_offset) {
void Get(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
std::memcpy(out_data_offset.data(), &data_offset, sizeof(s32));
R_SUCCEED();
std::vector<u8> buffer(sizeof(s32));
std::memcpy(buffer.data(), &data_offset, sizeof(s32));
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
Result Cancel() {
void Cancel(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
R_SUCCEED();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
Result GetErrorContext() {
void GetErrorContext(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
R_SUCCEED();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* completion_event{};
s32 data_offset;
@@ -139,7 +144,6 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
{4, nullptr, "SelectApplicationDesiredLanguage"},
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlData"},
{10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"},
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
};
@@ -156,7 +160,8 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}",
application_control_source, application_id);
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()};
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto size = out_buffer.size();
@@ -164,7 +169,8 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
const auto total_size = sizeof(FileSys::RawNACP) + icon_size;
if (size < total_size) {
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)", size);
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)",
size);
R_THROW(ResultUnknown);
}
@@ -172,7 +178,8 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
const auto bytes = control.first->GetRawBytes();
std::memcpy(out_buffer.data(), bytes.data(), bytes.size());
} else {
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero", application_id);
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero",
application_id);
std::memset(out_buffer.data(), 0, sizeof(FileSys::RawNACP));
}
@@ -197,12 +204,15 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage(
// Convert to application language, get priority list
const auto application_language = ConvertToApplicationLanguage(language_code);
if (application_language == std::nullopt) {
LOG_ERROR(Service_NS, "Could not convert application language! language_code={}", language_code);
LOG_ERROR(Service_NS, "Could not convert application language! language_code={}",
language_code);
R_THROW(Service::NS::ResultApplicationLanguageNotFound);
}
const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
if (!priority_list) {
LOG_ERROR(Service_NS, "Could not find application language priorities! application_language={}", *application_language);
LOG_ERROR(Service_NS,
"Could not find application language priorities! application_language={}",
*application_language);
R_THROW(Service::NS::ResultApplicationLanguageNotFound);
}
@@ -246,7 +256,8 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2(
const auto nacp_size = sizeof(FileSys::RawNACP);
if (size < nacp_size) {
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})", size, nacp_size);
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})",
size, nacp_size);
R_THROW(ResultUnknown);
}
@@ -297,81 +308,63 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2(
R_SUCCEED();
}
void IReadOnlyApplicationControlDataInterface::ListApplicationIcon(HLERequestContext& ctx) {
LOG_WARNING(Service_NS, "(stubbed)");
void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) {
/*
IPC::RequestParser rp{ctx};
auto control_source = rp.PopRaw<u8>();
rp.Skip(7, false);
auto transfer_memory_size = rp.Pop<u64>();
*/
const auto app_ids_buffer = ctx.ReadBuffer();
const u64 app_count = app_ids_buffer.size() / sizeof(u64);
const size_t app_count = app_ids_buffer.size() / sizeof(u64);
std::vector<u64> application_ids(app_count);
if (app_count > 0) {
std::memcpy(application_ids.data(), app_ids_buffer.data(), app_count * sizeof(u64));
}
auto t_mem_obj = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0));
auto* t_mem = t_mem_obj.GetPointerUnsafe();
size_t out_length = 0;
constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry);
const size_t total_data_size = app_count * title_entry_size;
constexpr s32 data_offset = 0;
if (t_mem != nullptr && t_mem->GetOwner() != nullptr && app_count > 0) {
auto& memory = t_mem->GetOwner()->GetMemory();
const auto t_mem_address = t_mem->GetSourceAddress();
// u64 - app count
memory.WriteBlock(t_mem_address + out_length, &app_count, sizeof(u64));
out_length += sizeof(u64);
// [list of u64] - size of icons
for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
u64 full_size = control.second->GetSize();
memory.WriteBlock(t_mem_address + out_length, &full_size, sizeof(u64));
out_length += sizeof(u64);
}
// [list of raw icon data]
std::vector<u8> full_icon_data;
for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
auto const full_size = control.second->GetSize();
if (full_size > 0) {
full_icon_data.resize(full_size);
control.second->Read(full_icon_data.data(), full_size, 0);
memory.WriteBlock(t_mem_address + out_length, full_icon_data.data(), full_size);
out_length += full_size;
}
}
}
auto async_value = std::make_shared<IAsyncValue>(system, 0, s32(out_length));
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, async_value->ReadableEvent());
rb.PushIpcInterface(ctx, std::move(async_value));
}
void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) {
const auto app_ids_buffer = ctx.ReadBuffer();
const size_t app_count = app_ids_buffer.size() / sizeof(u64);
auto t_mem_obj = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0));
auto* t_mem = t_mem_obj.GetPointerUnsafe();
constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry);
const size_t total_data_size = app_count * title_entry_size;
constexpr s32 data_offset = 0;
if (t_mem != nullptr && app_count > 0) {
auto& memory = system.ApplicationMemory();
const auto t_mem_address = t_mem->GetSourceAddress();
for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
const u64 app_id = application_ids[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
FileSys::LanguageEntry entry{};
if (control.first != nullptr) {
entry = control.first->GetLanguageEntry();
}
const size_t offset = i * title_entry_size;
memory.WriteBlock(t_mem_address + offset, &entry, title_entry_size);
}
}
auto async_value = std::make_shared<IAsyncValue>(system, data_offset, s32(total_data_size));
auto async_value = std::make_shared<IAsyncValueForListApplicationTitle>(
system, data_offset, static_cast<s32>(total_data_size));
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, async_value->ReadableEvent());
rb.PushIpcInterface(ctx, std::move(async_value));
}
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b, Out<u32> out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b,
Out<u32> out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
application_control_source, flag1, flag2, application_id);
@@ -34,7 +34,6 @@ public:
u8 flag1,
u8 flag2,
u64 application_id);
void ListApplicationIcon(HLERequestContext& ctx);
void ListApplicationTitle(HLERequestContext& ctx);
Result GetApplicationControlData3(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -16,8 +16,10 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S
static const FunctionInfo functions[] = {
{0, D<&IReadOnlyApplicationRecordInterface::HasApplicationRecord>, "HasApplicationRecord"},
{1, nullptr, "NotifyApplicationFailure"},
{2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, "IsDataCorruptedResult"},
{3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>, "ListApplicationRecord"},
{2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>,
"IsDataCorruptedResult"},
{3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>,
"ListApplicationRecord"},
};
// clang-format on
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/vulnerability_manager_interface.h"
namespace Service::NS {
IVulnerabilityManagerInterface::IVulnerabilityManagerInterface(Core::System& system_)
: ServiceFramework{system_, "ns:vm"} {
// clang-format off
static const FunctionInfo functions[] = {
{1200, D<&IVulnerabilityManagerInterface::NeedsUpdateVulnerability>, "NeedsUpdateVulnerability"},
{1201, nullptr, "UpdateSafeSystemVersionForDebug"},
{1202, nullptr, "GetSafeSystemVersion"},
};
// clang-format on
RegisterHandlers(functions);
}
IVulnerabilityManagerInterface::~IVulnerabilityManagerInterface() = default;
Result IVulnerabilityManagerInterface::NeedsUpdateVulnerability(
Out<bool> out_needs_update_vulnerability) {
LOG_WARNING(Service_NS, "(STUBBED) called");
*out_needs_update_vulnerability = false;
R_SUCCEED();
}
} // namespace Service::NS
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
namespace Service::NS {
class IVulnerabilityManagerInterface final
: public ServiceFramework<IVulnerabilityManagerInterface> {
public:
explicit IVulnerabilityManagerInterface(Core::System& system_);
~IVulnerabilityManagerInterface() override;
private:
Result NeedsUpdateVulnerability(Out<bool> out_needs_update_vulnerability);
};
} // namespace Service::NS
+18
View File
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/spl/csrng.h"
namespace Service::SPL {
CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_)
: Interface(system_, std::move(module_), "csrng") {
static const FunctionInfo functions[] = {
{0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"},
};
RegisterHandlers(functions);
}
CSRNG::~CSRNG() = default;
} // namespace Service::SPL
+20
View File
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/spl/spl_module.h"
namespace Core {
class System;
}
namespace Service::SPL {
class CSRNG final : public Module::Interface {
public:
explicit CSRNG(Core::System& system_, std::shared_ptr<Module> module_);
~CSRNG() override;
};
} // namespace Service::SPL
+1 -12
View File
@@ -13,6 +13,7 @@
#include "core/hle/api_version.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/spl/csrng.h"
#include "core/hle/service/spl/spl.h"
#include "core/hle/service/spl/spl_module.h"
@@ -202,18 +203,6 @@ Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item)
}
}
class CSRNG final : public Module::Interface {
public:
explicit CSRNG(Core::System& system_, std::shared_ptr<Module> module_)
: Interface(system_, std::move(module_), "csrng") {
static const FunctionInfo functions[] = {
{0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"},
};
RegisterHandlers(functions);
}
~CSRNG() override = default;
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto module = std::make_shared<Module>();
+39 -42
View File
@@ -101,10 +101,8 @@ struct Memory::Impl {
}
u64 protect_bytes = 0, protect_begin = 0;
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr + size) >> YUZU_PAGEBITS);
for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
const Common::PageType page_type = current_page_table->entries.GetUnchecked(addr >> YUZU_PAGEBITS).Type();
const Common::PageType page_type = current_page_table->entries[addr >> YUZU_PAGEBITS].ptr.Type();
switch (page_type) {
case Common::PageType::RasterizerCachedMemory:
if (protect_bytes > 0) {
@@ -125,14 +123,16 @@ struct Memory::Impl {
}
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr)
return reinterpret_cast<u8*>(paddr) + vaddr;
Common::PhysicalAddress const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr)
return reinterpret_cast<u8*>(paddr) + vaddr;
const Common::PhysicalAddress paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr != 0)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
@@ -243,12 +243,10 @@ struct Memory::Impl {
std::size_t page_index = addr >> YUZU_PAGEBITS;
std::size_t page_offset = addr & YUZU_PAGEMASK;
bool user_accessible = true;
current_page_table->entries.CommitRegion(page_index, page_index + (size >> YUZU_PAGEBITS) + 1);
while (remaining_size != 0) {
const std::size_t copy_amount = (std::min)(std::size_t(YUZU_PAGESIZE) - page_offset, remaining_size);
const auto current_vaddr = u64((page_index << YUZU_PAGEBITS) + page_offset);
const auto [pointer, type, _] = current_page_table->entries.GetUnchecked(page_index).PointerTypeBlock();
const auto [pointer, type] = current_page_table->entries[page_index].ptr.PointerType();
switch (type) {
case Common::PageType::Unmapped: {
user_accessible = false;
@@ -299,10 +297,10 @@ struct Memory::Impl {
}
[[nodiscard]] inline const u8* GetSpan(const VAddr addr, const std::size_t size) const noexcept {
return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr;
return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr;
}
[[nodiscard]] inline u8* GetSpan(const VAddr addr, const std::size_t size) noexcept {
return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr;
return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr;
}
bool WriteBlockImpl(const Common::ProcessAddress addr, const void* buffer, const std::size_t size, bool unsafe) {
@@ -406,14 +404,11 @@ struct Memory::Impl {
// The region is at a granularity of CPU pages.
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages);
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS);
const auto [pointer, type, block] = entry.PointerTypeBlock(true);
const Common::PageType page_type = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type();
if (debug) {
// Switch page type to debug if now debug
switch (type) {
switch (page_type) {
case Common::PageType::Unmapped:
ASSERT(false && "Attempted to mark unmapped pages as debug");
break;
@@ -422,14 +417,14 @@ struct Memory::Impl {
// Page is already marked.
break;
case Common::PageType::Memory:
entry.MarkDebug(pointer, block);
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::DebugMemory);
break;
default:
UNREACHABLE();
}
} else {
// Switch page type to non-debug if now non-debug
switch (type) {
switch (page_type) {
case Common::PageType::Unmapped:
ASSERT(false && "Attempted to mark unmapped pages as non-debug");
break;
@@ -438,7 +433,8 @@ struct Memory::Impl {
// Don't mess with already non-debug or rasterizer memory.
break;
case Common::PageType::DebugMemory: {
entry.Store(false, Common::PageType::Memory, block, pointer);
u8* const pointer = GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK);
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
break;
}
default:
@@ -470,10 +466,8 @@ struct Memory::Impl {
// is different). This assumes the specified GPU address region is contiguous as well.
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages);
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS);
const Common::PageType page_type = entry.Type();
const Common::PageType page_type= current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type();
if (cached) {
// Switch page type to cached if now cached
switch (page_type) {
@@ -483,7 +477,7 @@ struct Memory::Impl {
break;
case Common::PageType::DebugMemory:
case Common::PageType::Memory:
entry.MarkRasterizerCached();
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::RasterizerCachedMemory);
break;
case Common::PageType::RasterizerCachedMemory:
// There can be more than one GPU region mapped per CPU region, so it's common
@@ -505,13 +499,13 @@ struct Memory::Impl {
// that this area is already unmarked as cached.
break;
case Common::PageType::RasterizerCachedMemory: {
if (auto [ptr, _, block] = entry.PointerTypeBlock(true); ptr == 0) {
if (u8* const pointer = GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK); pointer == nullptr) {
// It's possible that this function has been called while updating the
// pagetable after unmapping a VMA. In that case the underlying VMA will no
// longer exist, and we should just leave the pagetable entry blank.
entry.Store(false, Common::PageType::Unmapped, block, 0);
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::Unmapped);
} else {
entry.Store(false, Common::PageType::Memory, block, ptr);
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
}
break;
}
@@ -545,18 +539,22 @@ struct Memory::Impl {
ASSERT_MSG(type != Common::PageType::Memory,
"Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
page_table.entries.ZeroRegion(base, end);
} else {
auto current_block = block_count.fetch_add(1, std::memory_order_relaxed);
ASSERT(current_block != 65535);
page_table.entries.CommitRegion(base, end);
while (base != end) {
auto host_ptr = reinterpret_cast<u64>(system.DeviceMemory().GetPointer<u8>(target)) - (base << YUZU_PAGEBITS);;
auto& entry = page_table.entries.GetUnchecked(base);
page_table.entries[base].ptr.Store(0, type);
page_table.entries[base].addr = 0;
page_table.entries[base].block = 0;
base += 1;
}
} else {
auto orig_base = base;
while (base != end) {
auto host_ptr = uintptr_t(system.DeviceMemory().GetPointer<u8>(target)) - (base << YUZU_PAGEBITS);
auto backing = GetInteger(target) - (base << YUZU_PAGEBITS);
page_table.entries[base].ptr.Store(host_ptr, type);
page_table.entries[base].addr = backing;
page_table.entries[base].block = orig_base << YUZU_PAGEBITS;
entry.Store(false, type, current_block, host_ptr);
ASSERT_MSG(page_table.entries[base].Pointer(),
ASSERT_MSG(page_table.entries[base].ptr.Pointer(),
"memory mapping base yield a nullptr within the table");
base += 1;
@@ -571,11 +569,11 @@ struct Memory::Impl {
vaddr &= 0xffffffffffffULL;
if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] {
// Avoid adding any extra logic to this fast-path block
const auto raw = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Raw();
if (auto pointer = Common::PageTable::PageEntryData::ExtractPointer(raw); pointer) [[likely]] {
const uintptr_t raw_pointer = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Raw();
if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) [[likely]] {
return reinterpret_cast<u8*>(pointer + vaddr);
} else {
switch (static_cast<Common::PageType>(raw.type)) {
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:#016x}", vaddr);
return nullptr;
@@ -775,7 +773,6 @@ struct Memory::Impl {
#else
Common::HostMemory* host_buffer{};
#endif
std::atomic<u16> block_count = 0;
};
Memory::Memory(Core::System& system_) : system{system_} {
@@ -814,7 +811,7 @@ bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const {
if (page >= page_table.entries.size()) {
return false;
}
const auto [pointer, type, _] = page_table.entries[page].PointerTypeBlock();
const auto [pointer, type] = page_table.entries[page].ptr.PointerType();
return pointer != 0 || type == Common::PageType::RasterizerCachedMemory ||
type == Common::PageType::DebugMemory;
}
@@ -371,10 +371,8 @@ EmitConfig A32AddressSpace::GetEmitConfig() {
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
.page_table_address_space_bits = 32,
.page_table_pointer_mask = conf.page_table_pointer_mask,
.page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits,
.page_table_log2_stride = conf.page_table_log2_stride,
.page_table_marked_bit = conf.page_table_marked_bit,
.page_table_sign_extension = conf.page_table_sign_extension,
.silently_mirror_page_table = true,
.absolute_offset_page_table = conf.absolute_offset_page_table,
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
@@ -545,10 +545,8 @@ EmitConfig A64AddressSpace::GetEmitConfig() {
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
.page_table_address_space_bits = conf.page_table_address_space_bits,
.page_table_pointer_mask = conf.page_table_pointer_mask,
.page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits,
.page_table_log2_stride = conf.page_table_log2_stride,
.page_table_marked_bit = conf.page_table_marked_bit,
.page_table_sign_extension = conf.page_table_sign_extension,
.silently_mirror_page_table = conf.silently_mirror_page_table,
.absolute_offset_page_table = conf.absolute_offset_page_table,
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
@@ -128,10 +128,8 @@ struct EmitConfig {
// Page table
u64 page_table_pointer;
std::size_t page_table_address_space_bits;
u64 page_table_pointer_mask;
int page_table_pointer_mask_bits;
std::size_t page_table_log2_stride;
std::optional<std::uint8_t> page_table_marked_bit;
std::optional<std::uint8_t> page_table_sign_extension;
bool silently_mirror_page_table;
bool absolute_offset_page_table;
u8 detect_misaligned_access_via_page_table;
@@ -273,18 +273,9 @@ std::pair<oaknut::XReg, oaknut::XReg> InlinePageTableEmitVAddrLookup(oaknut::Cod
// load x0 = *<(u8*)pagetable + index>
code.LDR(Xscratch0, Xpagetable, Xscratch0);
if (ctx.conf.page_table_marked_bit) {
// check for marked bit
code.TBNZ(Xscratch0, *ctx.conf.page_table_marked_bit, *fallback);
}
if (ctx.conf.page_table_pointer_mask != 0) {
code.AND(Xscratch0, Xscratch0, ctx.conf.page_table_pointer_mask);
}
// TODO: combine this with page_table_pointer_mask
if (ctx.conf.page_table_sign_extension) {
code.SBFM(Xscratch0, Xscratch0, 0, *ctx.conf.page_table_sign_extension);
if (ctx.conf.page_table_pointer_mask_bits != 0) {
const u64 mask = u64(~u64(0)) << ctx.conf.page_table_pointer_mask_bits;
code.AND(Xscratch0, Xscratch0, mask);
}
code.CBZ(Xscratch0, *fallback);
@@ -9,7 +9,6 @@
#pragma once
#include <bit>
#include <utility>
#include "dynarmic/backend/x64/xbyak.h"
#include "dynarmic/backend/x64/a32_emit_x64.h"
@@ -79,40 +78,27 @@ Xbyak::RegExp EmitVAddrLookup(BlockOfCode& code, EmitContext& ctx, size_t bitsiz
template<>
[[maybe_unused]] Xbyak::RegExp EmitVAddrLookup<A32EmitContext>(BlockOfCode& code, A32EmitContext& ctx, size_t bitsize, Xbyak::Label& abort, Xbyak::Reg64 vaddr) {
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code);
const Xbyak::Reg32 tmp = ctx.conf.absolute_offset_page_table ? page.cvt32() : ctx.reg_alloc.ScratchGpr(code).cvt32();
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp);
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp.cvt64());
code.mov(tmp, vaddr);
// TODO: This code assumes vaddr has been zext from 32-bits to 64-bits.
code.mov(tmp, vaddr.cvt32());
code.shr(tmp, int(page_table_const_bits));
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
code.mov(page, qword[r14 + tmp.cvt64()]);
// check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit);
code.jc(abort, code.T_NEAR);
}
// mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) {
if (ctx.conf.page_table_pointer_mask_bits == 0) {
code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask);
} else {
code.mov(tmp, ctx.conf.page_table_pointer_mask);
code.and_(page, tmp);
code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits);
}
if (ctx.conf.page_table_sign_extension) {
code.shl(page, *ctx.conf.page_table_sign_extension);
code.sar(page, *ctx.conf.page_table_sign_extension);
}
code.jz(abort, code.T_NEAR);
if (ctx.conf.absolute_offset_page_table) {
return page + vaddr;
}
code.mov(tmp, vaddr);
code.and_(tmp, u32(page_table_const_mask));
code.mov(tmp, vaddr.cvt32());
code.and_(tmp, static_cast<u32>(page_table_const_mask));
return page + tmp.cvt64();
}
@@ -122,7 +108,7 @@ template<>
const size_t unused_top_bits = 64 - ctx.conf.page_table_address_space_bits;
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code);
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table ? page : ctx.reg_alloc.ScratchGpr(code);
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp);
@@ -157,26 +143,11 @@ template<>
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
code.mov(page, qword[r14 + tmp]);
// check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit);
code.jc(abort, code.T_NEAR);
}
// mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) {
if (ctx.conf.page_table_pointer_mask_bits == 0) {
code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask);
} else {
code.mov(tmp, ctx.conf.page_table_pointer_mask);
code.and_(page, tmp);
code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits);
}
if (ctx.conf.page_table_sign_extension) {
code.shl(page, *ctx.conf.page_table_sign_extension);
code.sar(page, *ctx.conf.page_table_sign_extension);
}
code.jz(abort, code.T_NEAR);
if (ctx.conf.absolute_offset_page_table) {
return page + vaddr;
@@ -159,23 +159,14 @@ struct UserConfig {
/// Maximum size is limited by the maximum length of a x86_64 / arm64 jump.
std::uint32_t code_cache_size = 128 * 1024 * 1024; // bytes
/// Applies a bit mask to the bits in host pointers from the page table.
/// Masks out the first N bits in host pointers from the page table.
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
/// same integer and update the pointer attribute pair atomically.
/// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes.
std::uint64_t page_table_pointer_mask = 0;
/// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes.
std::int32_t page_table_pointer_mask_bits = 0;
/// Log2 of the size per page entry, value should be either 3 or 4
std::uint32_t page_table_log2_stride = 3;
/// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table.
/// If the bit is set to 1, Dynarmic will treat it as unmapped.
/// This bit should be included as part of `page_table_pointer_mask_bits`.
std::optional<std::uint8_t> page_table_marked_bit = std::nullopt;
/// If this value is set, Dynarmic will sign extend the page table pointer by this bit.
/// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`.
std::optional<std::uint8_t> page_table_sign_extension = std::nullopt;
// Log2 of the size per page entry, value should be either 3 or 4
std::size_t page_table_log2_stride = 3;
/// Select the architecture version to use.
/// There are minor behavioural differences between versions.
@@ -173,23 +173,14 @@ struct UserConfig {
/// This is only used if page_table is not nullptr.
std::uint32_t page_table_address_space_bits = 36;
/// Applies a bit mask to the bits in host pointers from the page table.
/// Masks out the first N bits in host pointers from the page table.
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
/// same integer and update the pointer attribute pair atomically.
/// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes.
std::uint64_t page_table_pointer_mask = 0;
/// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes.
std::int32_t page_table_pointer_mask_bits = 0;
/// Log2 of the size per page entry, value should be either 3 or 4
std::uint32_t page_table_log2_stride = 3;
/// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table.
/// If the bit is set to 1, Dynarmic will treat it as unmapped.
/// This bit should be included as part of `page_table_pointer_mask`.
std::optional<std::uint8_t> page_table_marked_bit = std::nullopt;
/// If this value is set, Dynarmic will sign extend the page table pointer by this bit.
/// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`.
std::optional<std::uint8_t> page_table_sign_extension = std::nullopt;
// Log2 of the size per page entry, value should be either 3 or 4
std::size_t page_table_log2_stride = 3;
/// Counter-timer frequency register. The value of the register is not interpreted by
/// dynarmic.
+2
View File
@@ -321,6 +321,8 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit,
uni_env.PadCodeMem();
jit_env.modified_memory.clear();
uni_env.modified_memory.clear();
jit_env.interrupts.clear();
uni_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
@@ -176,6 +176,8 @@ static void RunTestInstance(Dynarmic::A64::Jit& jit, A64Unicorn& uni, A64TestEnv
uni_env.code_mem_start_address = instructions_start;
jit_env.modified_memory.clear();
uni_env.modified_memory.clear();
jit_env.interrupts.clear();
uni_env.interrupts.clear();
const u64 initial_sp = RandInt<u64>(0x30'0000'0000, 0x40'0000'0000) * 4;
+10 -4
View File
@@ -282,17 +282,20 @@ int main(int argc, char** argv) {
return 1;
}
const char* const hex_instruction = [s = argv[2]] {
if (strlen(s) > 2 && s[0] == '0' && s[1] == 'x')
return s + 2;
return s;
const char* const hex_instruction = [argv] {
if (strlen(argv[2]) > 2 && argv[2][0] == '0' && argv[2][1] == 'x') {
return argv[2] + 2;
}
return argv[2];
}();
if (strlen(hex_instruction) > 8) {
fmt::print("hex string too long\n");
return 1;
}
const u32 instruction = strtol(hex_instruction, nullptr, 16);
if (strcmp(argv[1], "a32") == 0) {
PrintA32Instruction(instruction);
} else if (strcmp(argv[1], "a64") == 0) {
@@ -303,11 +306,13 @@ int main(int argc, char** argv) {
fmt::print("Invalid mode: {}\nValid values: a32, a64, thumb\n", argv[1]);
return 1;
}
if (argc == 4) {
if (strcmp(argv[3], "-exec") != 0) {
fmt::print("Invalid option {}\n", argv[3]);
return 1;
}
if (strcmp(argv[1], "a32") == 0) {
ExecuteA32Instruction(instruction);
} else {
@@ -315,5 +320,6 @@ int main(int argc, char** argv) {
return 1;
}
}
return 0;
}
+24 -40
View File
@@ -354,8 +354,6 @@ u32 GenRandomA64Inst(u64 pc, bool is_last_inst) {
"MSR_reg",
"MSR_imm",
"MRS",
// generates F16
"FCADD_vec",
};
for (const auto& [fn, bitstring] : list) {
@@ -442,6 +440,7 @@ void RunTestInstance(Dynarmic::A32::Jit& jit,
std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words);
jit_env.PadCodeMem();
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
@@ -511,6 +510,7 @@ void RunTestInstance(Dynarmic::A64::Jit& jit,
jit_env.code_mem.emplace_back(0x14000000); // B .
jit_env.code_mem_start_address = start_address;
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.SetRegisters(regs);
jit.SetVectors(vecs);
@@ -536,52 +536,36 @@ void RunTestInstance(Dynarmic::A64::Jit& jit,
fmt::print("initial_regs:");
for (u64 i : regs)
fmt::print(" {:016x}", i);
fmt::print(
"\n"
"initial_vecs:"
);
fmt::print("\n");
fmt::print("initial_vecs:");
for (auto i : vecs)
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
fmt::print(
"\n"
"initial_sp: {:016x}\n"
"initial_pstate: {:08x}\n"
"initial_fpcr: {:08x}\n"
"final_regs:"
, initial_sp
, pstate
, fpcr
);
fmt::print("\n");
fmt::print("initial_sp: {:016x}\n", initial_sp);
fmt::print("initial_pstate: {:08x}\n", pstate);
fmt::print("initial_fpcr: {:08x}\n", fpcr);
fmt::print("final_regs:");
for (u64 i : jit.GetRegisters())
fmt::print(" {:016x}", i);
fmt::print(
"\n"
"final_vecs:"
);
fmt::print("\n");
fmt::print("final_vecs:");
for (auto i : jit.GetVectors())
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
fmt::print(
"\n"
"final_sp: {:016x}\n"
"final_pc: {:016x}\n"
"final_pstate: {:08x}\n"
"final_fpcr: {:08x}\n"
"final_qc : {}\n"
"mod_mem:"
, jit.GetSP()
, jit.GetPC()
, jit.GetPstate()
, jit.GetFpcr()
, FP::FPSR{jit.GetFpsr()}.QC()
);
fmt::print("\n");
fmt::print("final_sp: {:016x}\n", jit.GetSP());
fmt::print("final_pc: {:016x}\n", jit.GetPC());
fmt::print("final_pstate: {:08x}\n", jit.GetPstate());
fmt::print("final_fpcr: {:08x}\n", jit.GetFpcr());
fmt::print("final_qc : {}\n", FP::FPSR{jit.GetFpsr()}.QC());
fmt::print("mod_mem:");
for (auto [addr, value] : jit_env.modified_memory)
fmt::print(" {:08x}:{:02x}", addr, value);
fmt::print(
"\n"
"===\n"
"{}"
, jit.Disassemble()
);
fmt::print("\n");
fmt::print("interrupts:\n");
for (const auto& i : jit_env.interrupts)
std::puts(i.c_str());
fmt::print("===\n");
fmt::print("{}", jit.Disassemble());
}
}
+2
View File
@@ -99,6 +99,7 @@ void RunTestInstance(Dynarmic::A32::Jit& jit,
std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words);
jit_env.PadCodeMem();
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
@@ -191,6 +192,7 @@ void RunTestInstance(A64::Jit& jit,
jit_env.code_mem.emplace_back(0x14000000); // B .
jit_env.code_mem_start_address = start_address;
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.SetRegisters(regs);
jit.SetVectors(vecs);
+3
View File
@@ -31,3 +31,6 @@ endif()
if (SOLARIS)
target_link_libraries(network PRIVATE socket nsl)
endif()
if (MINGW)
target_link_libraries(network PRIVATE winmm)
endif()
+3 -2
View File
@@ -28,8 +28,9 @@ bool CheckGameFirmware(u64 program_id) {
!FirmwareManager::CheckFirmwarePresence(*system)) {
auto result = QtCommon::Frontend::Warning(
tr("Game Requires Firmware"),
tr("This game may require firmware to function properly, and you don't have any installed. "
"Please install firmware before launching, or press \"OK\" to launch anyways."),
tr("The game you are trying to launch requires firmware to boot or to get past the "
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
"dump and install firmware</a>, or press \"OK\" to launch anyways."),
QtCommon::Frontend::Ok | QtCommon::Frontend::Cancel);
return result == QtCommon::Frontend::Ok;
+2 -2
View File
@@ -46,8 +46,8 @@ MemoryManager::MemoryManager(Core::System& system_, MaxwellDeviceMemoryManager&
page_table_mask = page_table_size - 1;
big_page_table_mask = big_page_table_size - 1;
big_page_table_dev.ResizeAndClear(big_page_table_size);
big_entries.resize(big_page_table_size / 32, 0);
big_page_table_dev.resize(big_page_table_size);
big_page_continuous.resize(big_page_table_size / continuous_bits, 0);
entries.resize(page_table_size / 32, 0);
}
@@ -143,7 +143,7 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr
const DAddr current_dev_addr = dev_addr + offset;
const auto index = PageEntryIndex(current_gpu_addr, true);
const u32 sub_value = static_cast<u32>(current_dev_addr >> cpu_page_bits);
big_page_table_dev.Set(index, sub_value);
big_page_table_dev[index] = sub_value;
const bool is_continuous = ([&] {
uintptr_t base_ptr{
reinterpret_cast<uintptr_t>(memory.GetPointer<u8>(current_dev_addr))};
+2 -2
View File
@@ -17,7 +17,7 @@
#include "common/multi_level_page_table.h"
#include "common/range_map.h"
#include "common/scratch_buffer.h"
#include "common/sparse_large_vector.h"
#include "common/virtual_buffer.h"
#include "video_core/invalidation_accumulator.h"
#include "video_core/cache_types.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
@@ -214,7 +214,7 @@ private:
Common::MultiLevelPageTable<u32> page_table;
Common::RangeMap<GPUVAddr, PTEKind> kind_map;
Common::SparseLargeVector<u32> big_page_table_dev;
Common::VirtualBuffer<u32> big_page_table_dev;
std::vector<u64> big_page_continuous;
boost::container::small_vector<std::pair<DAddr, std::size_t>, 32> page_stash{};
+9 -3
View File
@@ -233,7 +233,8 @@
</property>
<addaction name="action_Report_Compatibility"/>
<addaction name="action_Open_Mods_Page"/>
<addaction name="action_Open_UserHandbook"/>
<addaction name="action_Open_Quickstart_Guide"/>
<addaction name="action_Open_FAQ"/>
<addaction name="separator"/>
<addaction name="action_About"/>
<addaction name="action_Eden_Dependencies"/>
@@ -406,9 +407,14 @@
<string>Open &amp;Mods Page</string>
</property>
</action>
<action name="action_Open_UserHandbook">
<action name="action_Open_Quickstart_Guide">
<property name="text">
<string>&amp;User Handbook</string>
<string>Open &amp;Quickstart Guide</string>
</property>
</action>
<action name="action_Open_FAQ">
<property name="text">
<string>&amp;FAQ</string>
</property>
</action>
<action name="action_Capture_Screenshot">
+14 -7
View File
@@ -1506,7 +1506,8 @@ void MainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Pause, &MainWindow::OnPauseContinueGame);
connect_menu(ui->action_Stop, &MainWindow::OnStopGame);
connect_menu(ui->action_Open_Mods_Page, &MainWindow::OnOpenModsPage);
connect_menu(ui->action_Open_UserHandbook, &MainWindow::OnOpenUserHandbook);
connect_menu(ui->action_Open_Quickstart_Guide, &MainWindow::OnOpenQuickstartGuide);
connect_menu(ui->action_Open_FAQ, &MainWindow::OnOpenFAQ);
connect_menu(ui->action_Restart, &MainWindow::OnRestartGame);
connect_menu(ui->action_Configure, &MainWindow::OnConfigure);
connect_menu(ui->action_Configure_Current_Game, &MainWindow::OnConfigurePerGame);
@@ -1842,10 +1843,12 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
case Core::SystemResultStatus::ErrorVideoCore:
QMessageBox::critical(
this, tr("An error occurred initializing the video core."),
tr("This is usually caused by outdated GPU drivers. "
"Please see the log for more details. See: "
"<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>"
"How to access log files</a>."));
tr("Eden has encountered an error while running the video core. "
"This is usually caused by outdated GPU drivers, including integrated ones. "
"Please see the log for more details. "
"For more information on accessing the log, please see the following page: "
"<a href='https://yuzu-mirror.github.io/help/reference/log-files/'>"
"How to Upload the Log File</a>. "));
break;
default:
if (result > Core::SystemResultStatus::ErrorLoader) {
@@ -3196,8 +3199,12 @@ void MainWindow::OnOpenModsPage() {
OpenURL(QUrl(QStringLiteral("https://github.com/eden-emulator/yuzu-mod-archive")));
}
void MainWindow::OnOpenUserHandbook() {
OpenURL(QUrl(QStringLiteral("https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md")));
void MainWindow::OnOpenQuickstartGuide() {
OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help/quickstart/")));
}
void MainWindow::OnOpenFAQ() {
OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help")));
}
void MainWindow::ToggleFullscreen() {
+2 -1
View File
@@ -336,7 +336,8 @@ private slots:
void OnPrepareForSleep(bool prepare_sleep);
void OnMenuReportCompatibility();
void OnOpenModsPage();
void OnOpenUserHandbook();
void OnOpenQuickstartGuide();
void OnOpenFAQ();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path, u64 program_id);