Compare commits

...

5 commits

Author SHA1 Message Date
citrabot 51ab517bb4 Canary #2756 2024-01-22 00:27:26 +00:00
citrabot 830f5db540 Merge PR 7377 2024-01-22 00:27:26 +00:00
citrabot f9811740fd Merge PR 7375 2024-01-22 00:27:26 +00:00
citrabot 8943852230 Merge PR 7368 2024-01-22 00:27:26 +00:00
citrabot 6d2f4c03d6 Merge PR 7367 2024-01-22 00:27:26 +00:00
31 changed files with 1866 additions and 378 deletions

View file

@ -80,6 +80,10 @@ option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ON "NOT APPLE" OFF)
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
@ -255,20 +259,22 @@ find_package(tsl-robin-map QUIET)
# ======================================
if (APPLE)
if (NOT USE_SYSTEM_MOLTENVK)
download_moltenvk()
endif()
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
if (NOT IOS)
# Umbrella framework for everything GUI-related
find_library(COCOA_LIBRARY Cocoa REQUIRED)
endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
if (ENABLE_VULKAN)
if (NOT USE_SYSTEM_MOLTENVK)
download_moltenvk()
endif()
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif()
elseif (WIN32)
set(PLATFORM_LIBRARIES winmm ws2_32)
if (MINGW)

View file

@ -1,3 +1,15 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
| [7367](https://github.com/citra-emu/citra//pull/7367) | [`008a0b294`](https://github.com/citra-emu/citra//pull/7367/files) | pica_core: Propagate vertex uniforms to geometry setup when not in exclusive mode | [GPUCode](https://github.com/GPUCode/) | Yes |
| [7368](https://github.com/citra-emu/citra//pull/7368) | [`39441a53b`](https://github.com/citra-emu/citra//pull/7368/files) | cam: Ensure camera implementation is not null before using it | [GPUCode](https://github.com/GPUCode/) | Yes |
| [7375](https://github.com/citra-emu/citra//pull/7375) | [`8ff80b7e2`](https://github.com/citra-emu/citra//pull/7375/files) | build: Add flags to toggle specific renderer backends. | [Steveice10](https://github.com/Steveice10/) | Yes |
| [7377](https://github.com/citra-emu/citra//pull/7377) | [`9ddac92fa`](https://github.com/citra-emu/citra//pull/7377/files) | Implement NEWS service | [DaniElectra](https://github.com/DaniElectra/) | Yes |
End of merge log. You can find the original README.md below the break.
-----
<h1 align="center">
<br>
<a href="https://citra-emu.org/"><img src="https://raw.githubusercontent.com/citra-emu/citra-assets/master/Main/citra_logo.svg" alt="Citra" width="200"></a>

View file

@ -120,29 +120,6 @@ if (MSVC)
add_subdirectory(getopt)
endif()
# Glad
add_subdirectory(glad)
# glslang
if(USE_SYSTEM_GLSLANG)
find_package(glslang REQUIRED)
add_library(glslang INTERFACE)
add_library(SPIRV INTERFACE)
target_link_libraries(glslang INTERFACE glslang::glslang)
target_link_libraries(SPIRV INTERFACE glslang::SPIRV)
# System include path is different from submodule include path
get_target_property(GLSLANG_PREFIX glslang::SPIRV INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(SPIRV SYSTEM INTERFACE "${GLSLANG_PREFIX}/glslang")
else()
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang)
endif()
# inih
if(USE_SYSTEM_INIH)
find_package(inih REQUIRED COMPONENTS inih inir)
@ -197,9 +174,6 @@ if(NOT USE_SYSTEM_SOUNDTOUCH)
target_compile_definitions(SoundTouch PUBLIC SOUNDTOUCH_INTEGER_SAMPLES)
endif()
# sirit
add_subdirectory(sirit EXCLUDE_FROM_ALL)
# Teakra
add_subdirectory(teakra EXCLUDE_FROM_ALL)
@ -371,32 +345,64 @@ if (ENABLE_OPENAL)
endif()
endif()
# VMA
if(USE_SYSTEM_VMA)
add_library(vma INTERFACE)
find_package(VulkanMemoryAllocator REQUIRED)
if(TARGET GPUOpen::VulkanMemoryAllocator)
message(STATUS "Found VulkanMemoryAllocator")
target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator)
endif()
else()
add_library(vma INTERFACE)
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
# OpenGL dependencies
if (ENABLE_OPENGL)
# Glad
add_subdirectory(glad)
endif()
# vulkan-headers
add_library(vulkan-headers INTERFACE)
if(USE_SYSTEM_VULKAN_HEADERS)
find_package(Vulkan REQUIRED)
if(TARGET Vulkan::Headers)
message(STATUS "Found Vulkan headers")
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
# Vulkan dependencies
if (ENABLE_VULKAN)
# glslang
if(USE_SYSTEM_GLSLANG)
find_package(glslang REQUIRED)
add_library(glslang INTERFACE)
add_library(SPIRV INTERFACE)
target_link_libraries(glslang INTERFACE glslang::glslang)
target_link_libraries(SPIRV INTERFACE glslang::SPIRV)
# System include path is different from submodule include path
get_target_property(GLSLANG_PREFIX glslang::SPIRV INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(SPIRV SYSTEM INTERFACE "${GLSLANG_PREFIX}/glslang")
else()
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang)
endif()
else()
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
endif()
# adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools)
# sirit
add_subdirectory(sirit EXCLUDE_FROM_ALL)
# VMA
if(USE_SYSTEM_VMA)
add_library(vma INTERFACE)
find_package(VulkanMemoryAllocator REQUIRED)
if(TARGET GPUOpen::VulkanMemoryAllocator)
message(STATUS "Found VulkanMemoryAllocator")
target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator)
endif()
else()
add_library(vma INTERFACE)
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
endif()
# vulkan-headers
add_library(vulkan-headers INTERFACE)
if(USE_SYSTEM_VULKAN_HEADERS)
find_package(Vulkan REQUIRED)
if(TARGET Vulkan::Headers)
message(STATUS "Found Vulkan headers")
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
endif()
else()
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
endif()
# adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools)
endif()
endif()

View file

@ -159,8 +159,14 @@ else()
endif()
endif()
if (NOT APPLE)
add_compile_definitions(HAS_OPENGL)
if(ENABLE_SOFTWARE_RENDERER)
add_compile_definitions(ENABLE_SOFTWARE_RENDERER)
endif()
if(ENABLE_OPENGL)
add_compile_definitions(ENABLE_OPENGL)
endif()
if(ENABLE_VULKAN)
add_compile_definitions(ENABLE_VULKAN)
endif()
add_subdirectory(common)

View file

@ -19,10 +19,6 @@ add_library(citra-android SHARED
default_ini.h
emu_window/emu_window.cpp
emu_window/emu_window.h
emu_window/emu_window_gl.cpp
emu_window/emu_window_gl.h
emu_window/emu_window_vk.cpp
emu_window/emu_window_vk.h
game_info.cpp
game_settings.cpp
game_settings.h
@ -35,10 +31,23 @@ add_library(citra-android SHARED
)
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics log mediandk yuv)
target_link_libraries(citra-android PRIVATE android camera2ndk inih jnigraphics log mediandk yuv)
if ("arm64" IN_LIST ARCHITECTURE)
target_link_libraries(citra-android PRIVATE adrenotools)
if (ENABLE_OPENGL)
target_sources(citra-android PRIVATE
emu_window/emu_window_gl.cpp
emu_window/emu_window_gl.h
)
target_link_libraries(citra-android PRIVATE EGL glad)
endif()
if (ENABLE_VULKAN)
target_sources(citra-android PRIVATE
emu_window/emu_window_vk.cpp
emu_window/emu_window_vk.h
)
if ("arm64" IN_LIST ARCHITECTURE)
target_link_libraries(citra-android PRIVATE adrenotools)
endif()
endif()
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android)

View file

@ -45,8 +45,12 @@
#include "jni/camera/ndk_camera.h"
#include "jni/camera/still_image_camera.h"
#include "jni/config.h"
#ifdef ENABLE_OPENGL
#include "jni/emu_window/emu_window_gl.h"
#endif
#ifdef ENABLE_VULKAN
#include "jni/emu_window/emu_window_vk.h"
#endif
#include "jni/game_settings.h"
#include "jni/id_cache.h"
#include "jni/input_manager.h"
@ -55,7 +59,7 @@
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
#if CITRA_ARCH(arm64)
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
#include <adrenotools/driver.h>
#endif
@ -142,15 +146,29 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
const auto graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
break;
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Unknown graphics API {}, using Vulkan", graphics_api);
LOG_CRITICAL(Frontend,
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
#elif ENABLE_VULKAN
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
break;
}
// Forces a config reload on game boot, if the user changed settings in the UI
@ -239,7 +257,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir) {
#if CITRA_ARCH(arm64)
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
void* handle{};
const char* file_redirect_dir_{};
int featureFlags{};

View file

@ -8,25 +8,42 @@ add_executable(citra
default_ini.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2_sw.cpp
emu_window/emu_window_sdl2_sw.h
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
precompiled_headers.h
resource.h
)
if (ENABLE_SOFTWARE_RENDERER)
target_sources(citra PRIVATE
emu_window/emu_window_sdl2_sw.cpp
emu_window/emu_window_sdl2_sw.h
)
endif()
if (ENABLE_OPENGL)
target_sources(citra PRIVATE
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
)
endif()
if (ENABLE_VULKAN)
target_sources(citra PRIVATE
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
)
endif()
create_target_directory_groups(citra)
target_link_libraries(citra PRIVATE citra_common citra_core input_common network)
target_link_libraries(citra PRIVATE inih glad)
target_link_libraries(citra PRIVATE inih)
if (MSVC)
target_link_libraries(citra PRIVATE getopt)
endif()
target_link_libraries(citra PRIVATE ${PLATFORM_LIBRARIES} SDL2::SDL2 Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(citra PRIVATE glad)
endif()
if(UNIX AND NOT APPLE)
install(TARGETS citra RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()

View file

@ -13,9 +13,15 @@
#include "citra/config.h"
#include "citra/emu_window/emu_window_sdl2.h"
#ifdef ENABLE_OPENGL
#include "citra/emu_window/emu_window_sdl2_gl.h"
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
#include "citra/emu_window/emu_window_sdl2_sw.h"
#endif
#ifdef ENABLE_VULKAN
#include "citra/emu_window/emu_window_sdl2_vk.h"
#endif
#include "common/common_paths.h"
#include "common/detached_tasks.h"
#include "common/file_util.h"
@ -354,16 +360,36 @@ int main(int argc, char** argv) {
const auto create_emu_window = [&](bool fullscreen,
bool is_secondary) -> std::unique_ptr<EmuWindow_SDL2> {
switch (Settings::values.graphics_api.GetValue()) {
const auto graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary);
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
return std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, is_secondary);
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
case Settings::GraphicsAPI::Software:
return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary);
#endif
default:
LOG_CRITICAL(
Frontend,
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary);
#elif ENABLE_VULKAN
return std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, is_secondary);
#elif ENABLE_SOFTWARE_RENDERER
return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary);
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
}
LOG_ERROR(Frontend, "Invalid Graphics API, using OpenGL");
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary);
};
const auto emu_window{create_emu_window(fullscreen, false)};

View file

@ -185,10 +185,15 @@ add_executable(citra-qt
util/spinbox.h
util/util.cpp
util/util.h
util/vk_device_info.cpp
util/vk_device_info.h
)
if (ENABLE_VULKAN)
target_sources(citra-qt PRIVATE
util/vk_device_info.cpp
util/vk_device_info.h
)
endif()
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
@ -300,8 +305,16 @@ endif()
create_target_directory_groups(citra-qt)
target_link_libraries(citra-qt PRIVATE audio_core citra_common citra_core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads vulkan-headers)
target_link_libraries(citra-qt PRIVATE Boost::boost nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(citra-qt PRIVATE glad)
endif()
if (ENABLE_VULKAN)
target_link_libraries(citra-qt PRIVATE vulkan-headers)
endif()
if (NOT WIN32)
target_include_directories(citra-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})

View file

@ -26,7 +26,7 @@
#include "video_core/renderer_base.h"
#include "video_core/renderer_software/renderer_software.h"
#ifdef HAS_OPENGL
#ifdef ENABLE_OPENGL
#include <glad/glad.h>
#include <QOffscreenSurface>
@ -137,7 +137,7 @@ void EmuThread::run() {
#endif
}
#ifdef HAS_OPENGL
#ifdef ENABLE_OPENGL
static std::unique_ptr<QOpenGLContext> CreateQOpenGLContext(bool gles) {
QSurfaceFormat format;
if (gles) {
@ -258,7 +258,7 @@ public:
virtual ~RenderWidget() = default;
};
#ifdef HAS_OPENGL
#ifdef ENABLE_OPENGL
class OpenGLRenderWidget : public RenderWidget {
public:
explicit OpenGLRenderWidget(GRenderWindow* parent, Core::System& system_, bool is_secondary)
@ -305,6 +305,7 @@ private:
};
#endif
#ifdef ENABLE_VULKAN
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
@ -324,7 +325,9 @@ public:
return nullptr;
}
};
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
struct SoftwareRenderWidget : public RenderWidget {
explicit SoftwareRenderWidget(GRenderWindow* parent, Core::System& system_)
: RenderWidget(parent), system(system_) {}
@ -377,6 +380,7 @@ struct SoftwareRenderWidget : public RenderWidget {
private:
Core::System& system;
};
#endif
static Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
@ -636,17 +640,40 @@ bool GRenderWindow::InitRenderTarget() {
const auto graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
#ifdef ENABLE_SOFTWARE_RENDERER
case Settings::GraphicsAPI::Software:
InitializeSoftware();
break;
#endif
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
if (!InitializeOpenGL() || !LoadOpenGL()) {
return false;
}
break;
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
InitializeVulkan();
break;
#endif
default:
LOG_CRITICAL(Frontend,
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
if (!InitializeOpenGL() || !LoadOpenGL()) {
return false;
}
#elif ENABLE_VULKAN
InitializeVulkan();
#elif ENABLE_SOFTWARE_RENDERER
InitializeSoftware();
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
break;
}
// Update the Window System information with the new render target
@ -700,8 +727,8 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
setMinimumSize(minimal_size.first, minimal_size.second);
}
#ifdef ENABLE_OPENGL
bool GRenderWindow::InitializeOpenGL() {
#ifdef HAS_OPENGL
if (!QOpenGLContext::supportsThreadedOpenGL()) {
QMessageBox::warning(this, tr("OpenGL not available!"),
tr("OpenGL shared contexts are not supported."));
@ -726,33 +753,13 @@ bool GRenderWindow::InitializeOpenGL() {
child_widget->windowHandle()->setFormat(format);
return true;
#else
QMessageBox::warning(this, tr("OpenGL not available!"),
tr("Citra has not been compiled with OpenGL support."));
return false;
#endif
}
void GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
}
void GRenderWindow::InitializeSoftware() {
child_widget = new SoftwareRenderWidget(this, system);
main_context = std::make_unique<DummyContext>();
}
#ifdef HAS_OPENGL
static void* GetProcAddressGL(const char* name) {
return reinterpret_cast<void*>(QOpenGLContext::currentContext()->getProcAddress(name));
}
#endif
bool GRenderWindow::LoadOpenGL() {
#ifdef HAS_OPENGL
auto context = CreateSharedContext();
auto scope = context->Acquire();
const auto gles = context->IsGLES();
@ -785,12 +792,24 @@ bool GRenderWindow::LoadOpenGL() {
}
return true;
#else
QMessageBox::warning(this, tr("OpenGL not available!"),
tr("Citra has not been compiled with OpenGL support."));
return false;
#endif
}
#endif
#ifdef ENABLE_VULKAN
void GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
}
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
void GRenderWindow::InitializeSoftware() {
child_widget = new SoftwareRenderWidget(this, system);
main_context = std::make_unique<DummyContext>();
}
#endif
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread;
@ -805,7 +824,7 @@ void GRenderWindow::showEvent(QShowEvent* event) {
}
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
#ifdef HAS_OPENGL
#ifdef ENABLE_OPENGL
const auto graphics_api = Settings::values.graphics_api.GetValue();
if (graphics_api == Settings::GraphicsAPI::OpenGL) {
auto gl_context = static_cast<OpenGLSharedContext*>(main_context.get());

View file

@ -186,10 +186,16 @@ private:
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
#ifdef ENABLE_OPENGL
bool InitializeOpenGL();
void InitializeVulkan();
void InitializeSoftware();
bool LoadOpenGL();
#endif
#ifdef ENABLE_VULKAN
void InitializeVulkan();
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
void InitializeSoftware();
#endif
QWidget* child_widget = nullptr;

View file

@ -13,7 +13,9 @@
#include "common/logging/backend.h"
#include "common/settings.h"
#include "ui_configure_debug.h"
#ifdef ENABLE_VULKAN
#include "video_core/renderer_vulkan/vk_instance.h"
#endif
// The QSlider doesn't have an easy way to set a custom step amount,
// so we can just convert from the sliders range (0 - 79) to the expected
@ -36,6 +38,7 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
#ifdef ENABLE_VULKAN
connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) {
if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) {
try {
@ -65,6 +68,7 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
}
}
});
#endif
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
ui->toggle_renderer_debug->setEnabled(!is_powered_on);

View file

@ -7,7 +7,9 @@
#include "citra_qt/configuration/configure_enhancements.h"
#include "common/settings.h"
#include "ui_configure_enhancements.h"
#ifdef ENABLE_OPENGL
#include "video_core/renderer_opengl/post_processing_opengl.h"
#endif
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureEnhancements>()) {
@ -117,12 +119,14 @@ void ConfigureEnhancements::updateShaders(Settings::StereoRenderOption stereo_op
ui->shader_combobox->setCurrentIndex(0);
#ifdef ENABLE_OPENGL
for (const auto& shader : OpenGL::GetPostProcessingShaderList(
stereo_option == Settings::StereoRenderOption::Anaglyph)) {
ui->shader_combobox->addItem(QString::fromStdString(shader));
if (current_shader == shader)
ui->shader_combobox->setCurrentIndex(ui->shader_combobox->count() - 1);
}
#endif
}
void ConfigureEnhancements::RetranslateUI() {

View file

@ -3,11 +3,14 @@
// Refer to the license.txt file included.
#include <QColorDialog>
#include <QStandardItemModel>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_graphics.h"
#include "common/settings.h"
#include "ui_configure_graphics.h"
#ifdef ENABLE_VULKAN
#include "video_core/renderer_vulkan/vk_instance.h"
#endif
ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices, bool is_powered_on,
QWidget* parent)
@ -27,12 +30,32 @@ ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices,
// Set the index to -1 to ensure the below lambda is called with setCurrentIndex
ui->graphics_api_combo->setCurrentIndex(-1);
auto graphics_api_combo_model =
qobject_cast<QStandardItemModel*>(ui->graphics_api_combo->model());
#ifndef ENABLE_SOFTWARE_RENDERER
const auto software_item =
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Software));
software_item->setFlags(software_item->flags() & ~Qt::ItemIsEnabled);
#endif
#ifndef ENABLE_OPENGL
const auto opengl_item =
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::OpenGL));
opengl_item->setFlags(opengl_item->flags() & ~Qt::ItemIsEnabled);
#endif
#ifndef ENABLE_VULKAN
const auto vulkan_item =
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Vulkan));
vulkan_item->setFlags(vulkan_item->flags() & ~Qt::ItemIsEnabled);
#else
if (physical_devices.empty()) {
const u32 index = static_cast<u32>(Settings::GraphicsAPI::Vulkan);
ui->graphics_api_combo->removeItem(index);
const auto vulkan_item =
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Vulkan));
vulkan_item->setFlags(vulkan_item->flags() & ~Qt::ItemIsEnabled);
ui->physical_device_combo->setVisible(false);
ui->spirv_shader_gen->setVisible(false);
}
#endif
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int index) {

View file

@ -519,6 +519,7 @@ void GameList::UpdateColumnVisibility() {
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size_column);
}
#ifdef ENABLE_OPENGL
void ForEachOpenGLCacheFile(u64 program_id, auto func) {
for (const std::string_view cache_type : {"separable", "conventional"}) {
const std::string path = fmt::format("{}opengl/precompiled/{}/{:016X}.bin",
@ -528,6 +529,7 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) {
func(file);
}
}
#endif
void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name,
u64 program_id, u64 extdata_id, Service::FS::MediaType media_type) {
@ -545,8 +547,10 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
QMenu* shader_menu = context_menu.addMenu(tr("Disk Shader Cache"));
QAction* open_shader_cache_location = shader_menu->addAction(tr("Open Shader Cache Location"));
shader_menu->addSeparator();
#ifdef ENABLE_OPENGL
QAction* delete_opengl_disk_shader_cache =
shader_menu->addAction(tr("Delete OpenGL Shader Cache"));
#endif
QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall"));
QAction* uninstall_all = uninstall_menu->addAction(tr("Everything"));
@ -564,9 +568,11 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
const bool is_application = program_id_high == 0x00040000 || program_id_high == 0x00040002 ||
program_id_high == 0x00040010;
#ifdef ENABLE_OPENGL
bool opengl_cache_exists = false;
ForEachOpenGLCacheFile(
program_id, [&opengl_cache_exists](QFile& file) { opengl_cache_exists |= file.exists(); });
#endif
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
open_save_location->setEnabled(
@ -603,7 +609,9 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
open_mods_location->setEnabled(is_application);
dump_romfs->setEnabled(is_application);
#ifdef ENABLE_OPENGL
delete_opengl_disk_shader_cache->setEnabled(opengl_cache_exists);
#endif
uninstall_all->setEnabled(is_installed || has_update || has_dlc);
uninstall_game->setEnabled(is_installed);
@ -669,9 +677,11 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
emit OpenFolderRequested(program_id, GameListOpenTarget::SHADER_CACHE);
}
});
#ifdef ENABLE_OPENGL
connect(delete_opengl_disk_shader_cache, &QAction::triggered, this, [program_id] {
ForEachOpenGLCacheFile(program_id, [](QFile& file) { file.remove(); });
});
#endif
connect(uninstall_all, &QAction::triggered, this, [=, this] {
QMessageBox::StandardButton answer = QMessageBox::question(
this, tr("Citra"),

View file

@ -270,6 +270,7 @@ GMainWindow::GMainWindow(Core::System& system_)
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity);
#ifdef ENABLE_VULKAN
physical_devices = GetVulkanPhysicalDevices();
if (physical_devices.empty()) {
QMessageBox::warning(this, tr("No Suitable Vulkan Devices Detected"),
@ -277,6 +278,7 @@ GMainWindow::GMainWindow(Core::System& system_)
"Your GPU may not support Vulkan 1.1, or you do not "
"have the latest graphics driver."));
}
#endif
#if ENABLE_QT_UPDATER
if (UISettings::values.check_for_update_on_start) {
@ -2689,6 +2691,22 @@ void GMainWindow::UpdateAPIIndicator(bool update) {
u32 api_index = static_cast<u32>(Settings::values.graphics_api.GetValue());
if (update) {
api_index = (api_index + 1) % graphics_apis.size();
// Skip past any disabled renderers.
#ifndef ENABLE_SOFTWARE_RENDERER
if (api_index == static_cast<u32>(Settings::GraphicsAPI::Software)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
#ifndef ENABLE_OPENGL
if (api_index == static_cast<u32>(Settings::GraphicsAPI::OpenGL)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
#ifndef ENABLE_VULKAN
if (api_index == static_cast<u32>(Settings::GraphicsAPI::Vulkan)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
}
@ -3164,8 +3182,11 @@ int main(int argc, char* argv[]) {
chdir(bin_path.c_str());
#endif
#ifdef ENABLE_OPENGL
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
QApplication app(argc, argv);
// Qt changes the locale and causes issues in float conversion using std::to_string() when

View file

@ -115,6 +115,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, Y2R) \
SUB(Service, PS) \
SUB(Service, PLGLDR) \
SUB(Service, NEWS) \
CLS(HW) \
SUB(HW, Memory) \
SUB(HW, LCD) \

View file

@ -82,6 +82,7 @@ enum class Class : u8 {
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
Service_PS, ///< The PS (Process) service
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
Service_NEWS, ///< The NEWS (Notifications) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation

View file

@ -449,13 +449,19 @@ struct Values {
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
// Renderer
SwitchableSetting<GraphicsAPI, true> graphics_api{
#ifdef HAS_OPENGL
SwitchableSetting<GraphicsAPI, true> graphics_api {
#if defined(ENABLE_OPENGL)
GraphicsAPI::OpenGL,
#else
#elif defined(ENABLE_VULKAN)
GraphicsAPI::Vulkan,
#elif defined(ENABLE_SOFTWARE_RENDERER)
GraphicsAPI::Software,
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
GraphicsAPI::Software, GraphicsAPI::Vulkan, "graphics_api"};
GraphicsAPI::Software, GraphicsAPI::Vulkan, "graphics_api"
};
SwitchableSetting<u32> physical_device{0, "physical_device"};
Setting<bool> use_gles{false, "use_gles"};
Setting<bool> renderer_debug{false, "renderer_debug"};

View file

@ -10,7 +10,6 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/camera/factory.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/process.h"
@ -77,10 +76,10 @@ constexpr std::array<int, 13> LATENCY_BY_FRAME_RATE{{
33, // Rate_30_To_10
}};
const Result ERROR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
const Result ERROR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::CAM,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
constexpr Result ResultInvalidEnumValue(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
constexpr Result ResultOutOfRange(ErrorDescription::OutOfRange, ErrorModule::CAM,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
void Module::PortConfig::Clear() {
completion_event->Clear();
@ -261,27 +260,32 @@ void Module::Interface::StartCapture(Kernel::HLERequestContext& ctx) {
if (port_select.IsValid()) {
for (int i : port_select) {
if (!cam->ports[i].is_busy) {
if (!cam->ports[i].is_active) {
// This doesn't return an error, but seems to put the camera in an undefined
// state
LOG_ERROR(Service_CAM, "port {} hasn't been activated", i);
} else {
cam->cameras[cam->ports[i].camera_id].impl->StartCapture();
cam->ports[i].is_busy = true;
if (cam->ports[i].is_pending_receiving) {
cam->ports[i].is_pending_receiving = false;
cam->StartReceiving(i);
}
}
} else {
auto& port = cam->ports[i];
if (port.is_busy) {
LOG_WARNING(Service_CAM, "port {} already started", i);
continue;
}
if (!port.is_active) {
// This doesn't return an error, but seems to put the camera in an undefined
// state
LOG_ERROR(Service_CAM, "port {} hasn't been activated", i);
continue;
}
auto& camera = cam->cameras[port.camera_id];
if (!camera.impl) {
cam->LoadCameraImplementation(camera, port.camera_id);
}
camera.impl->StartCapture();
port.is_busy = true;
if (port.is_pending_receiving) {
port.is_pending_receiving = false;
cam->StartReceiving(i);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, port_select={}", port_select.m_val);
@ -306,7 +310,7 @@ void Module::Interface::StopCapture(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, port_select={}", port_select.m_val);
@ -328,7 +332,7 @@ void Module::Interface::IsBusy(Kernel::HLERequestContext& ctx) {
rb.Push(is_busy);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.Skip(1, false);
}
@ -356,7 +360,7 @@ void Module::Interface::GetVsyncInterruptEvent(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(cam->ports[port].vsync_interrupt_event);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.PushCopyObjects<Kernel::Object>(nullptr);
}
@ -374,7 +378,7 @@ void Module::Interface::GetBufferErrorInterruptEvent(Kernel::HLERequestContext&
rb.PushCopyObjects(cam->ports[port].buffer_error_interrupt_event);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.PushCopyObjects<Kernel::Object>(nullptr);
}
@ -409,7 +413,7 @@ void Module::Interface::SetReceiving(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(port.completion_event);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.PushCopyObjects<Kernel::Object>(nullptr);
}
@ -429,7 +433,7 @@ void Module::Interface::IsFinishedReceiving(Kernel::HLERequestContext& ctx) {
rb.Push(!is_busy);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.Skip(1, false);
}
@ -451,7 +455,7 @@ void Module::Interface::SetTransferLines(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_WARNING(Service_CAM, "(STUBBED) called, port_select={}, lines={}, width={}, height={}",
@ -469,7 +473,7 @@ void Module::Interface::GetMaxLines(Kernel::HLERequestContext& ctx) {
constexpr u32 MIN_TRANSFER_UNIT = 256;
constexpr u32 MAX_BUFFER_SIZE = 2560;
if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
rb.Push(ERROR_OUT_OF_RANGE);
rb.Push(ResultOutOfRange);
rb.Skip(1, false);
} else {
u32 lines = MAX_BUFFER_SIZE / width;
@ -480,7 +484,7 @@ void Module::Interface::GetMaxLines(Kernel::HLERequestContext& ctx) {
while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) {
--lines;
if (lines == 0) {
result = ERROR_OUT_OF_RANGE;
result = ResultOutOfRange;
break;
}
}
@ -506,7 +510,7 @@ void Module::Interface::SetTransferBytes(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_WARNING(Service_CAM, "(STUBBED)called, port_select={}, bytes={}, width={}, height={}",
@ -524,7 +528,7 @@ void Module::Interface::GetTransferBytes(Kernel::HLERequestContext& ctx) {
rb.Push(cam->ports[port].transfer_bytes);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.Skip(1, false);
}
@ -542,7 +546,7 @@ void Module::Interface::GetMaxBytes(Kernel::HLERequestContext& ctx) {
constexpr u32 MIN_TRANSFER_UNIT = 256;
constexpr u32 MAX_BUFFER_SIZE = 2560;
if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
rb.Push(ERROR_OUT_OF_RANGE);
rb.Push(ResultOutOfRange);
rb.Skip(1, false);
} else {
u32 bytes = MAX_BUFFER_SIZE;
@ -571,7 +575,7 @@ void Module::Interface::SetTrimming(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, port_select={}, trim={}", port_select.m_val, trim);
@ -588,7 +592,7 @@ void Module::Interface::IsTrimming(Kernel::HLERequestContext& ctx) {
rb.Push(cam->ports[port].is_trimming);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.Skip(1, false);
}
@ -614,7 +618,7 @@ void Module::Interface::SetTrimmingParams(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, port_select={}, x0={}, y0={}, x1={}, y1={}", port_select.m_val,
@ -635,7 +639,7 @@ void Module::Interface::GetTrimmingParams(Kernel::HLERequestContext& ctx) {
rb.Push(cam->ports[port].y1);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
rb.Skip(4, false);
}
@ -661,7 +665,7 @@ void Module::Interface::SetTrimmingParamsCenter(Kernel::HLERequestContext& ctx)
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid port_select={}", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, port_select={}, trim_w={}, trim_h={}, cam_w={}, cam_h={}",
@ -687,7 +691,7 @@ void Module::Interface::Activate(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else if (camera_select[0] && camera_select[1]) {
LOG_ERROR(Service_CAM, "camera 0 and 1 can't be both activated");
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
} else {
if (camera_select[0]) {
cam->ActivatePort(0, 0);
@ -702,7 +706,7 @@ void Module::Interface::Activate(Kernel::HLERequestContext& ctx) {
}
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}", camera_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}", camera_select.m_val);
@ -728,7 +732,7 @@ void Module::Interface::SwitchContext(Kernel::HLERequestContext& ctx) {
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}, context_select={}", camera_select.m_val,
@ -738,24 +742,29 @@ void Module::Interface::SwitchContext(Kernel::HLERequestContext& ctx) {
void Module::Interface::FlipImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const CameraSet camera_select(rp.Pop<u8>());
const Flip flip = static_cast<Flip>(rp.Pop<u8>());
const Flip flip = rp.PopEnum<Flip>();
const ContextSet context_select(rp.Pop<u8>());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid() && context_select.IsValid()) {
for (int camera : camera_select) {
for (int index : camera_select) {
auto& camera = cam->cameras[index];
for (int context : context_select) {
cam->cameras[camera].contexts[context].flip = flip;
if (cam->cameras[camera].current_context == context) {
cam->cameras[camera].impl->SetFlip(flip);
camera.contexts[context].flip = flip;
if (camera.current_context != context) {
continue;
}
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetFlip(flip);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}, flip={}, context_select={}",
@ -776,19 +785,24 @@ void Module::Interface::SetDetailSize(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid() && context_select.IsValid()) {
for (int camera : camera_select) {
for (int index : camera_select) {
auto& camera = cam->cameras[index];
for (int context : context_select) {
cam->cameras[camera].contexts[context].resolution = resolution;
if (cam->cameras[camera].current_context == context) {
cam->cameras[camera].impl->SetResolution(resolution);
camera.contexts[context].resolution = resolution;
if (camera.current_context != context) {
continue;
}
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetResolution(resolution);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM,
@ -806,19 +820,24 @@ void Module::Interface::SetSize(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid() && context_select.IsValid()) {
for (int camera : camera_select) {
for (int index : camera_select) {
auto& camera = cam->cameras[index];
for (int context : context_select) {
cam->cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size];
if (cam->cameras[camera].current_context == context) {
cam->cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]);
camera.contexts[context].resolution = PRESET_RESOLUTION[size];
if (camera.current_context != context) {
continue;
}
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetResolution(PRESET_RESOLUTION[size]);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}, size={}, context_select={}",
@ -828,18 +847,22 @@ void Module::Interface::SetSize(Kernel::HLERequestContext& ctx) {
void Module::Interface::SetFrameRate(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const CameraSet camera_select(rp.Pop<u8>());
const FrameRate frame_rate = static_cast<FrameRate>(rp.Pop<u8>());
const FrameRate frame_rate = rp.PopEnum<FrameRate>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid()) {
for (int camera : camera_select) {
cam->cameras[camera].frame_rate = frame_rate;
cam->cameras[camera].impl->SetFrameRate(frame_rate);
for (int index : camera_select) {
auto& camera = cam->cameras[index];
camera.frame_rate = frame_rate;
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetFrameRate(frame_rate);
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}", camera_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select={}, frame_rate={}",
@ -849,24 +872,29 @@ void Module::Interface::SetFrameRate(Kernel::HLERequestContext& ctx) {
void Module::Interface::SetEffect(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const CameraSet camera_select(rp.Pop<u8>());
const Effect effect = static_cast<Effect>(rp.Pop<u8>());
const Effect effect = rp.PopEnum<Effect>();
const ContextSet context_select(rp.Pop<u8>());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid() && context_select.IsValid()) {
for (int camera : camera_select) {
for (int index : camera_select) {
auto& camera = cam->cameras[index];
for (int context : context_select) {
cam->cameras[camera].contexts[context].effect = effect;
if (cam->cameras[camera].current_context == context) {
cam->cameras[camera].impl->SetEffect(effect);
camera.contexts[context].effect = effect;
if (camera.current_context != context) {
continue;
}
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetEffect(effect);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}, effect={}, context_select={}",
@ -876,24 +904,29 @@ void Module::Interface::SetEffect(Kernel::HLERequestContext& ctx) {
void Module::Interface::SetOutputFormat(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const CameraSet camera_select(rp.Pop<u8>());
const OutputFormat format = static_cast<OutputFormat>(rp.Pop<u8>());
const OutputFormat format = rp.PopEnum<OutputFormat>();
const ContextSet context_select(rp.Pop<u8>());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (camera_select.IsValid() && context_select.IsValid()) {
for (int camera : camera_select) {
for (int index : camera_select) {
auto& camera = cam->cameras[index];
for (int context : context_select) {
cam->cameras[camera].contexts[context].format = format;
if (cam->cameras[camera].current_context == context) {
cam->cameras[camera].impl->SetFormat(format);
camera.contexts[context].format = format;
if (camera.current_context != context) {
continue;
}
if (!camera.impl) {
cam->LoadCameraImplementation(camera, index);
}
camera.impl->SetFormat(format);
}
}
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", camera_select.m_val,
context_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
rb.Push(ResultInvalidEnumValue);
}
LOG_DEBUG(Service_CAM, "called, camera_select={}, format={}, context_select={}",
@ -919,7 +952,7 @@ void Module::Interface::GetLatestVsyncTiming(Kernel::HLERequestContext& ctx) {
if (!port_select.IsSingle() || count > MaxVsyncTimings) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ERROR_OUT_OF_RANGE);
rb.Push(ResultOutOfRange);
rb.PushStaticBuffer({}, 0);
return;
}
@ -1003,7 +1036,7 @@ Result Module::SetPackageParameter(const PackageParameterType& package) {
} else {
LOG_ERROR(Service_CAM, "invalid camera_select={}, context_select={}", package.camera_select,
package.context_select);
return ERROR_INVALID_ENUM_VALUE;
return ResultInvalidEnumValue;
}
}

View file

@ -2,18 +2,787 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/archives.h"
#include "common/assert.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/file_backend.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/result.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/news/news.h"
#include "core/hle/service/news/news_s.h"
#include "core/hle/service/news/news_u.h"
#include "core/hle/service/service.h"
SERVICE_CONSTRUCT_IMPL(Service::NEWS::Module)
namespace Service::NEWS {
namespace ErrCodes {
enum {
/// This error is returned if either the NewsDB header or the header for a notification ID is
/// invalid
InvalidHeader = 5,
};
}
constexpr Result ErrorInvalidHeader = // 0xC8A12805
Result(ErrCodes::InvalidHeader, ErrorModule::News, ErrorSummary::InvalidState,
ErrorLevel::Status);
constexpr std::array<u8, 8> news_system_savedata_id{
0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00,
};
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) {
ar& db;
ar& notification_ids;
ar& automatic_sync_flag;
ar& news_system_save_data_archive;
}
SERIALIZE_IMPL(Module)
void Module::Interface::AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s) {
IPC::RequestParser rp(ctx);
const u32 header_size = rp.Pop<u32>();
const u32 message_size = rp.Pop<u32>();
const u32 image_size = rp.Pop<u32>();
u32 process_id;
if (!news_s) {
process_id = rp.PopPID();
LOG_INFO(Service_NEWS,
"called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}, process_id={}",
header_size, message_size, image_size, process_id);
} else {
LOG_INFO(Service_NEWS, "called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}",
header_size, message_size, image_size);
}
auto header_buffer = rp.PopMappedBuffer();
auto message_buffer = rp.PopMappedBuffer();
auto image_buffer = rp.PopMappedBuffer();
NotificationHeader header;
header_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(header_size)));
std::vector<u8> message(message_size);
message_buffer.Read(message.data(), 0, message.size());
std::vector<u8> image(image_size);
image_buffer.Read(image.data(), 0, image.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 6);
SCOPE_EXIT({
rb.PushMappedBuffer(header_buffer);
rb.PushMappedBuffer(message_buffer);
rb.PushMappedBuffer(image_buffer);
});
if (!news_s) {
// Set the program_id using the input process ID
auto fs_user = news->system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
ASSERT_MSG(fs_user != nullptr, "fs:USER service is missing.");
auto program_info_result = fs_user->GetProgramLaunchInfo(process_id);
if (program_info_result.Failed()) {
rb.Push(program_info_result.Code());
return;
}
header.program_id = program_info_result.Unwrap().program_id;
// The date_time is set by the sysmodule on news:u requests
auto& share_page = news->system.Kernel().GetSharedPageHandler();
header.date_time = share_page.GetSystemTimeSince2000();
}
const auto save_result = news->SaveNotification(&header, header_size, message.data(),
message_size, image.data(), image_size);
if (R_FAILED(save_result)) {
rb.Push(save_result);
return;
}
// Mark the DB header new notification flag
if ((news->db.header.flags & 1) == 0) {
news->db.header.flags |= 1;
const auto db_result = news->SaveNewsDBSavedata();
if (R_FAILED(db_result)) {
rb.Push(db_result);
return;
}
}
rb.Push(ResultSuccess);
}
void Module::Interface::AddNotification(Kernel::HLERequestContext& ctx) {
AddNotificationImpl(ctx, false);
}
void Module::Interface::AddNotificationSystem(Kernel::HLERequestContext& ctx) {
AddNotificationImpl(ctx, true);
}
void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
// Cleanup the sorted notification IDs
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
news->notification_ids[i] = i;
}
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
FileSys::Path archive_path(news_system_savedata_id);
// Format the SystemSaveData archive 0x00010035
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
// NOTE: The original sysmodule doesn't clear the News DB in memory
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(news->GetTotalNotifications());
}
void Module::Interface::SetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
NewsDBHeader header{};
input_buffer.Read(&header, 0, std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size)));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNewsDBHeader(&header, size));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
input_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
const auto result = news->SetNotificationHeader(notification_index, &header, size);
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(result);
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationMessage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> data(size);
input_buffer.Read(data.data(), 0, data.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNotificationMessage(notification_index, data.data(), size));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> data(size);
input_buffer.Read(data.data(), 0, data.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNotificationImage(notification_index, data.data(), size));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
NewsDBHeader db_header{};
const auto result = news->GetNewsDBHeader(&db_header, size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
} else {
const auto copied_size = result.Unwrap();
output_buffer.Write(&news->db.header, 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
rb.PushMappedBuffer(output_buffer);
}
void Module::Interface::GetNotificationHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
const auto result = news->GetNotificationHeader(notification_index, &header, size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout flag of the
// header with the result of boss:P command 0x0004070080 (possibly named
// GetOptoutFlagPrivileged?) using the program_id as parameter
const auto copied_size = result.Unwrap();
output_buffer.Write(&header, 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::GetNotificationMessage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> message(size);
const auto result = news->GetNotificationMessage(notification_index, message.data(), size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
const auto copied_size = result.Unwrap();
output_buffer.Write(message.data(), 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::GetNotificationImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> image(size);
const auto result = news->GetNotificationImage(notification_index, image.data(), size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
const auto copied_size = result.Unwrap();
output_buffer.Write(image.data(), 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u8 flag = rp.Pop<u8>();
LOG_INFO(Service_NEWS, "called flag=0x{:x}", flag);
news->automatic_sync_flag = flag;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::SetNotificationHeaderOther(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
output_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
const auto result = news->SetNotificationHeaderOther(notification_index, &header, size);
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(result);
rb.PushMappedBuffer(output_buffer);
}
void Module::Interface::WriteNewsDBSavedata(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(news->SaveNewsDBSavedata());
}
void Module::Interface::GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_WARNING(Service_NEWS, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(0); // Total number of pending BOSS notifications to be synced
}
u32 Module::GetTotalNotifications() {
u32 count = 0;
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
const u32 notification_id = notification_ids[i];
if (!db.notifications[notification_id].IsValid()) {
continue;
}
count++;
}
return count;
}
ResultVal<std::size_t> Module::GetNewsDBHeader(NewsDBHeader* header, const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), size);
std::memcpy(header, &db.header, copy_size);
return copy_size;
}
ResultVal<std::size_t> Module::GetNotificationHeader(const u32 notification_index,
NotificationHeader* header,
const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(header, &db.notifications[notification_id], copy_size);
return copy_size;
}
ResultVal<std::size_t> Module::GetNotificationMessage(const u32 notification_index, u8* message,
const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
const auto result = LoadFileFromSavedata(message_file, size, message);
if (result.Failed()) {
return result.Code();
}
return result.Unwrap();
}
ResultVal<std::size_t> Module::GetNotificationImage(const u32 notification_index, u8* image,
const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
const auto result = LoadFileFromSavedata(image_file, size, image);
if (result.Failed()) {
return result.Code();
}
return result.Unwrap();
}
Result Module::SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size) {
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size));
std::memcpy(&db.header, header, copy_size);
return SaveNewsDBSavedata();
}
Result Module::SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(&db.notifications[notification_id], header, copy_size);
return SaveNewsDBSavedata();
}
Result Module::SetNotificationHeaderOther(const u32 notification_index,
const NotificationHeader* header,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(&db.notifications[notification_id], header, copy_size);
return ResultSuccess;
}
Result Module::SetNotificationMessage(const u32 notification_index, const u8* message,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
return SaveFileToSavedata(message_file, size, message);
}
Result Module::SetNotificationImage(const u32 notification_index, const u8* image,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
return SaveFileToSavedata(image_file, size, image);
}
Result Module::SaveNotification(const NotificationHeader* header, const std::size_t header_size,
const u8* message, const std::size_t message_size, const u8* image,
const std::size_t image_size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (!header->IsValid()) {
return ErrorInvalidHeader;
}
u32 notification_count = GetTotalNotifications();
// If we have reached the limit of 100 notifications, delete the oldest one
if (notification_count >= MAX_NOTIFICATIONS) {
LOG_WARNING(Service_NEWS,
"Notification limit has been reached. Deleting oldest notification ID: {}",
notification_ids[0]);
R_TRY(DeleteNotification(notification_ids[0]));
notification_count--;
}
// Check if there is enough space for storing the new notification data. The header is already
// allocated with the News DB
const u64 needed_space = message_size + image_size;
while (notification_count > 0) {
const u64 free_space = news_system_save_data_archive->GetFreeBytes();
if (needed_space <= free_space) {
break;
}
LOG_WARNING(Service_NEWS, "Not enough space available. Deleting oldest notification ID: {}",
notification_ids[0]);
// If we don't have space, delete old notifications until we do
R_TRY(DeleteNotification(notification_ids[0]));
notification_count--;
}
LOG_DEBUG(Service_NEWS, "New notification: notification_id={}, title={}",
notification_ids[notification_count], Common::UTF16BufferToUTF8(header->title));
if (image != nullptr && image_size > 0) {
R_TRY(SetNotificationImage(notification_count, image, image_size));
}
if (message != nullptr && message_size > 0) {
R_TRY(SetNotificationMessage(notification_count, message, message_size));
}
R_TRY(SetNotificationHeader(notification_count, header, header_size));
// Sort the notifications after saving
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return this->CompareNotifications(first_id, second_id);
});
return ResultSuccess;
}
Result Module::DeleteNotification(const u32 notification_id) {
bool deleted = false;
// Check if the input notification ID exists, and clear it
if (db.notifications[notification_id].IsValid()) {
db.notifications[notification_id] = {};
R_TRY(SaveNewsDBSavedata());
deleted = true;
}
// Cleanup images and messages for invalid notifications
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
if (!db.notifications[i].IsValid()) {
const std::string image_file = fmt::format("/news{:03d}.mpo", i);
auto result = news_system_save_data_archive->DeleteFile(image_file);
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
return result;
}
const std::string message_file = fmt::format("/news{:03d}.txt", i);
result = news_system_save_data_archive->DeleteFile(message_file);
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
return result;
}
}
}
// If the input notification ID was deleted, reorder the notification IDs list
if (deleted) {
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return this->CompareNotifications(first_id, second_id);
});
}
return ResultSuccess;
}
Result Module::LoadNewsDBSavedata() {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
// Open the SystemSaveData archive 0x00010035
FileSys::Path archive_path(news_system_savedata_id);
auto archive_result = systemsavedata_factory.Open(archive_path, 0);
// If the archive didn't exist, create the files inside
if (archive_result.Code() == FileSys::ResultNotFound) {
// Format the archive to create the directories
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
// Open it again to get a valid archive now that the folder exists
news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
} else {
ASSERT_MSG(archive_result.Succeeded(), "Could not open the NEWS SystemSaveData archive!");
news_system_save_data_archive = std::move(archive_result).Unwrap();
}
const std::string news_db_file = "/news.db";
auto news_result =
LoadFileFromSavedata(news_db_file, sizeof(NewsDB), reinterpret_cast<u8*>(&db));
// Read the file if it already exists
if (news_result.Failed()) {
// Create the file immediately if it doesn't exist
db.header = {.valid = 1};
news_result = SaveFileToSavedata(news_db_file, sizeof(NewsDB), reinterpret_cast<u8*>(&db));
} else {
// Sort the notifications from the file
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return this->CompareNotifications(first_id, second_id);
});
}
return news_result.Code();
}
Result Module::SaveNewsDBSavedata() {
return SaveFileToSavedata("/news.db", sizeof(NewsDB), reinterpret_cast<u8*>(&db));
}
ResultVal<std::size_t> Module::LoadFileFromSavedata(std::string filename, std::size_t length,
u8* buffer) {
FileSys::Mode mode = {};
mode.read_flag.Assign(1);
FileSys::Path path(filename);
auto result = news_system_save_data_archive->OpenFile(path, mode);
if (result.Failed()) {
return result.Code();
}
auto file = std::move(result).Unwrap();
const auto bytes_read = file->Read(0, length, buffer);
file->Close();
ASSERT_MSG(bytes_read.Succeeded(), "could not read file");
return bytes_read.Unwrap();
}
Result Module::SaveFileToSavedata(std::string filename, std::size_t length, const u8* buffer) {
FileSys::Mode mode = {};
mode.write_flag.Assign(1);
mode.create_flag.Assign(1);
FileSys::Path path(filename);
auto result = news_system_save_data_archive->OpenFile(path, mode);
ASSERT_MSG(result.Succeeded(), "could not open file");
auto file = std::move(result).Unwrap();
file->Write(0, length, 1, buffer);
file->Close();
return ResultSuccess;
}
bool Module::CompareNotifications(const u32 first_id, const u32 second_id) {
// Notification IDs are sorted by date time, with valid notifications being first.
// This is done so that other system applications like the News applet can easily
// iterate over the notifications with an incrementing index.
ASSERT(first_id < MAX_NOTIFICATIONS && second_id < MAX_NOTIFICATIONS);
if (!db.notifications[first_id].IsValid()) {
return false;
}
if (!db.notifications[second_id].IsValid()) {
return true;
}
return db.notifications[first_id].date_time < db.notifications[second_id].date_time;
}
Module::Interface::Interface(std::shared_ptr<Module> news, const char* name, u32 max_session)
: ServiceFramework(name, max_session), news(std::move(news)) {}
Module::Module(Core::System& system_) : system(system_) {
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
notification_ids[i] = i;
}
LoadNewsDBSavedata();
}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
std::make_shared<NEWS_S>()->InstallAsService(service_manager);
std::make_shared<NEWS_U>()->InstallAsService(service_manager);
auto news = std::make_shared<Module>(system);
std::make_shared<NEWS_S>(news)->InstallAsService(service_manager);
std::make_shared<NEWS_U>(news)->InstallAsService(service_manager);
}
} // namespace Service::NEWS

View file

@ -4,12 +4,495 @@
#pragma once
#include "core/file_sys/archive_backend.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::NEWS {
constexpr u32 MAX_NOTIFICATIONS = 100;
struct NewsDBHeader {
u8 valid;
u8 flags;
INSERT_PADDING_BYTES(0xE);
bool IsValid() const {
return valid == 1;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& valid;
ar& flags;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NewsDBHeader) == 0x10, "News DB Header structure size is wrong");
struct NotificationHeader {
u8 flag_valid;
u8 flag_read;
u8 flag_jpeg;
u8 flag_boss;
u8 flag_optout;
u8 flag_url;
u8 flag_unk0x6;
INSERT_PADDING_BYTES(0x1);
u64_le program_id;
u32_le ns_data_id; // Only used in BOSS notifications
u32_le version; // Only used in BOSS notifications
u64_le jump_param;
INSERT_PADDING_BYTES(0x8);
u64_le date_time;
std::array<u16_le, 0x20> title;
bool IsValid() const {
return flag_valid == 1;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& flag_valid;
ar& flag_read;
ar& flag_jpeg;
ar& flag_boss;
ar& flag_optout;
ar& flag_url;
ar& flag_unk0x6;
ar& program_id;
ar& ns_data_id;
ar& version;
ar& jump_param;
ar& date_time;
ar& title;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NotificationHeader) == 0x70, "Notification Header structure size is wrong");
struct NewsDB {
NewsDBHeader header;
std::array<NotificationHeader, MAX_NOTIFICATIONS> notifications;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& header;
ar& notifications;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NewsDB) == 0x2BD0, "News DB structure size is wrong");
class Module final {
public:
explicit Module(Core::System& system_);
~Module() = default;
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> news, const char* name, u32 max_session);
~Interface() = default;
private:
void AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s);
protected:
/**
* AddNotification NEWS:U service function.
* Inputs:
* 0 : 0x000100C8
* 1 : Header size
* 2 : Message size
* 3 : Image size
* 4 : PID Translation Header (0x20)
* 5 : Caller PID
* 6 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 7 : Header Buffer Pointer
* 8 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 9 : Message Buffer Pointer
* 10 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 11 : Image Buffer Pointer
* Outputs:
* 0 : 0x00010046
* 1 : Result of function, 0 on success, otherwise error code
*/
void AddNotification(Kernel::HLERequestContext& ctx);
/**
* AddNotification NEWS:S service function.
* Inputs:
* 0 : 0x000100C6
* 1 : Header size
* 2 : Message size
* 3 : Image size
* 4 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 5 : Header Buffer Pointer
* 6 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 7 : Message Buffer Pointer
* 8 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 9 : Image Buffer Pointer
* Outputs:
* 0 : 0x00010046
* 1 : Result of function, 0 on success, otherwise error code
*/
void AddNotificationSystem(Kernel::HLERequestContext& ctx);
/**
* ResetNotifications service function.
* Inputs:
* 0 : 0x00040000
* Outputs:
* 0 : 0x00040040
* 1 : Result of function, 0 on success, otherwise error code
*/
void ResetNotifications(Kernel::HLERequestContext& ctx);
/**
* GetTotalNotifications service function.
* Inputs:
* 0 : 0x00050000
* Outputs:
* 0 : 0x00050080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of notifications
*/
void GetTotalNotifications(Kernel::HLERequestContext& ctx);
/**
* SetNewsDBHeader service function.
* Inputs:
* 0 : 0x00060042
* 1 : Size
* 2 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 3 : Input Buffer Pointer
* Outputs:
* 0 : 0x00060042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNewsDBHeader(Kernel::HLERequestContext& ctx);
/**
* SetNotificationHeader service function.
* Inputs:
* 0 : 0x00070082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00070042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationHeader(Kernel::HLERequestContext& ctx);
/**
* SetNotificationMessage service function.
* Inputs:
* 0 : 0x00080082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00080042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationMessage(Kernel::HLERequestContext& ctx);
/**
* SetNotificationImage service function.
* Inputs:
* 0 : 0x00090082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00090042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationImage(Kernel::HLERequestContext& ctx);
/**
* GetNewsDBHeader service function.
* Inputs:
* 0 : 0x000A0042
* 1 : Size
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 3 : Output Buffer Pointer
* Outputs:
* 0 : 0x000A0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
/**
* GetNotificationHeader service function.
* Inputs:
* 0 : 0x000B0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000B0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationHeader(Kernel::HLERequestContext& ctx);
/**
* GetNotificationMessage service function.
* Inputs:
* 0 : 0x000C0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000C0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationMessage(Kernel::HLERequestContext& ctx);
/**
* GetNotificationMessage service function.
* Inputs:
* 0 : 0x000D0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000D0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationImage(Kernel::HLERequestContext& ctx);
/**
* SetAutomaticSyncFlag service function.
* Inputs:
* 0 : 0x00110040
* 1 : Flag
* Outputs:
* 0 : 0x00110040
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx);
/**
* SetNotificationHeaderOther service function.
* Inputs:
* 0 : 0x00120082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00120042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationHeaderOther(Kernel::HLERequestContext& ctx);
/**
* WriteNewsDBSavedata service function.
* Inputs:
* 0 : 0x00130000
* Outputs:
* 0 : 0x00130040
* 1 : Result of function, 0 on success, otherwise error code
*/
void WriteNewsDBSavedata(Kernel::HLERequestContext& ctx);
/**
* GetTotalArrivedNotifications service function.
* Inputs:
* 0 : 0x00140000
* Outputs:
* 0 : 0x00140080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of pending notifications to be synced
*/
void GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> news;
};
private:
/**
* Gets the total number of notifications
* @returns Number of notifications
*/
u32 GetTotalNotifications();
/**
* Loads the News DB into the given buffer
* @param header The header buffer
* @param size The size of the header buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNewsDBHeader(NewsDBHeader* header, const std::size_t size);
/**
* Loads the header for a notification ID into the given buffer
* @param notification_index The index of the notification ID
* @param header The header buffer
* @param size The size of the header buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationHeader(const u32 notification_index,
NotificationHeader* header,
const std::size_t size);
/**
* Opens the message file for a notification ID and loads it to the message buffer
* @param notification_index The index of the notification ID
* @param mesasge The message buffer
* @param size The size of the message buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationMessage(const u32 notification_index, u8* message,
const std::size_t size);
/**
* Opens the image file for a notification ID and loads it to the image buffer
* @param notification_index The index of the notification ID
* @param image The image buffer
* @param size The size of the image buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationImage(const u32 notification_index, u8* image,
const std::size_t size);
/**
* Modifies the header for the News DB in memory and saves the News DB file
* @param header The database header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size);
/**
* Modifies the header for a notification ID on memory and saves the News DB file
* @param notification_index The index of the notification ID
* @param header The notification header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
const std::size_t size);
/**
* Modifies the header for a notification ID on memory. The News DB file isn't updated
* @param notification_index The index of the notification ID
* @param header The notification header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationHeaderOther(const u32 notification_index,
const NotificationHeader* header, const std::size_t size);
/**
* Sets a given message to a notification ID
* @param notification_index The index of the notification ID
* @param message The notification message
* @param size The size of the message buffer
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationMessage(const u32 notification_index, const u8* message,
const std::size_t size);
/**
* Sets a given image to a notification ID
* @param notification_index The index of the notification ID
* @param image The notification image
* @param size The size of the image buffer
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationImage(const u32 notification_index, const u8* image,
const std::size_t size);
/**
* Creates a new notification with the given data and saves all the contents
* @param header The notification header
* @param header_size The amount of bytes to copy from the header
* @param message The notification message
* @param message_size The size of the message buffer
* @param image The notification image
* @param image_size The size of the image buffer
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveNotification(const NotificationHeader* header, const std::size_t header_size,
const u8* message, const std::size_t message_size, const u8* image,
const std::size_t image_size);
/**
* Deletes the given notification ID from the database
* @param notification_id The notification ID to delete
* @returns Result indicating the result of the operation, 0 on success
*/
Result DeleteNotification(const u32 notification_id);
/**
* Opens the news.db savedata file and load it to the memory buffer. If the file or the savedata
* don't exist, they are created
* @returns Result indicating the result of the operation, 0 on success
*/
Result LoadNewsDBSavedata();
/**
* Writes the news.db savedata file to the the NEWS system savedata
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveNewsDBSavedata();
/**
* Opens the file with the given filename inside the NEWS system savedata
* @param filename The file to open
* @param length The buffer size
* @param buffer The buffer to output the contents on
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> LoadFileFromSavedata(std::string filename, std::size_t length,
u8* buffer);
/**
* Writes the file with the given filename inside the NEWS system savedata
* @param filename The output file
* @param length The buffer size
* @param buffer The buffer to read the contents from
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveFileToSavedata(std::string filename, std::size_t length, const u8* buffer);
bool CompareNotifications(const u32 first_id, const u32 second_id);
private:
Core::System& system;
NewsDB db{};
std::array<u32, MAX_NOTIFICATIONS> notification_ids; // Notifications ordered by date time
u8 automatic_sync_flag;
std::unique_ptr<FileSys::ArchiveBackend> news_system_save_data_archive;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
void InstallInterfaces(Core::System& system);
} // namespace Service::NEWS
SERVICE_CONSTRUCT(Service::NEWS::Module)
BOOST_CLASS_EXPORT_KEY(Service::NEWS::Module)

View file

@ -3,63 +3,33 @@
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/news/news_s.h"
SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_S)
namespace Service::NEWS {
struct NewsDbHeader {
u8 unknown_one;
u8 flags;
INSERT_PADDING_BYTES(0xE);
};
static_assert(sizeof(NewsDbHeader) == 0x10, "News DB Header structure size is wrong");
void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_WARNING(Service, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(0);
}
void NEWS_S::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_WARNING(Service, "(STUBBED) called size={}", size);
NewsDbHeader dummy = {.unknown_one = 1, .flags = 0};
output_buffer.Write(&dummy, 0, std::min(sizeof(NewsDbHeader), static_cast<std::size_t>(size)));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(size);
}
NEWS_S::NEWS_S() : ServiceFramework("news:s", 2) {
NEWS_S::NEWS_S(std::shared_ptr<Module> news) : Module::Interface(std::move(news), "news:s", 2) {
const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "AddNotification"},
{0x0001, &NEWS_S::AddNotificationSystem, "AddNotification"},
{0x0004, &NEWS_S::ResetNotifications, "ResetNotifications"},
{0x0005, &NEWS_S::GetTotalNotifications, "GetTotalNotifications"},
{0x0006, nullptr, "SetNewsDBHeader"},
{0x0007, nullptr, "SetNotificationHeader"},
{0x0008, nullptr, "SetNotificationMessage"},
{0x0009, nullptr, "SetNotificationImage"},
{0x0006, &NEWS_S::SetNewsDBHeader, "SetNewsDBHeader"},
{0x0007, &NEWS_S::SetNotificationHeader, "SetNotificationHeader"},
{0x0008, &NEWS_S::SetNotificationMessage, "SetNotificationMessage"},
{0x0009, &NEWS_S::SetNotificationImage, "SetNotificationImage"},
{0x000A, &NEWS_S::GetNewsDBHeader, "GetNewsDBHeader"},
{0x000B, nullptr, "GetNotificationHeader"},
{0x000C, nullptr, "GetNotificationMessage"},
{0x000D, nullptr, "GetNotificationImage"},
{0x000B, &NEWS_S::GetNotificationHeader, "GetNotificationHeader"},
{0x000C, &NEWS_S::GetNotificationMessage, "GetNotificationMessage"},
{0x000D, &NEWS_S::GetNotificationImage, "GetNotificationImage"},
{0x000E, nullptr, "SetInfoLEDPattern"},
{0x0012, nullptr, "GetNotificationHeaderOther"},
{0x0013, nullptr, "WriteNewsDBSavedata"},
{0x000F, nullptr, "SyncArrivedNotifications"},
{0x0010, nullptr, "SyncOneArrivedNotification"},
{0x0011, &NEWS_S::SetAutomaticSyncFlag, "SetAutomaticSyncFlag"},
{0x0012, &NEWS_S::SetNotificationHeaderOther, "SetNotificationHeaderOther"},
{0x0013, &NEWS_S::WriteNewsDBSavedata, "WriteNewsDBSavedata"},
{0x0014, &NEWS_S::GetTotalArrivedNotifications, "GetTotalArrivedNotifications"},
// clang-format on
};
RegisterHandlers(functions);

View file

@ -4,44 +4,19 @@
#pragma once
#include <memory>
#include "core/hle/service/service.h"
#include "core/hle/service/news/news.h"
namespace Service::NEWS {
class NEWS_S final : public ServiceFramework<NEWS_S> {
class NEWS_S final : public Module::Interface {
public:
NEWS_S();
explicit NEWS_S(std::shared_ptr<Module> news);
private:
/**
* GetTotalNotifications service function.
* Inputs:
* 0 : 0x00050000
* Outputs:
* 0 : 0x00050080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of notifications
*/
void GetTotalNotifications(Kernel::HLERequestContext& ctx);
/**
* GetNewsDBHeader service function.
* Inputs:
* 0 : 0x000A0042
* 1 : Size
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 3 : Output Buffer Pointer
* Outputs:
* 0 : 0x000A0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
SERVICE_SERIALIZATION_SIMPLE
SERVICE_SERIALIZATION(NEWS_S, news, Module)
};
} // namespace Service::NEWS
BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_S)
BOOST_SERIALIZATION_CONSTRUCT(Service::NEWS::NEWS_S)

View file

@ -9,10 +9,10 @@ SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_U)
namespace Service::NEWS {
NEWS_U::NEWS_U() : ServiceFramework("news:u", 1) {
NEWS_U::NEWS_U(std::shared_ptr<Module> news) : Module::Interface(std::move(news), "news:u", 1) {
const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "AddNotification"},
{0x0001, &NEWS_U::AddNotification, "AddNotification"},
// clang-format on
};
RegisterHandlers(functions);

View file

@ -4,19 +4,19 @@
#pragma once
#include <memory>
#include "core/hle/service/service.h"
#include "core/hle/service/news/news.h"
namespace Service::NEWS {
class NEWS_U final : public ServiceFramework<NEWS_U> {
class NEWS_U final : public Module::Interface {
public:
NEWS_U();
explicit NEWS_U(std::shared_ptr<Module> news);
private:
SERVICE_SERIALIZATION_SIMPLE
SERVICE_SERIALIZATION(NEWS_U, news, Module)
};
} // namespace Service::NEWS
BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_U)
BOOST_SERIALIZATION_CONSTRUCT(Service::NEWS::NEWS_U)

View file

@ -60,94 +60,9 @@ add_library(video_core STATIC
rasterizer_cache/texture_cube.h
rasterizer_cache/utils.cpp
rasterizer_cache/utils.h
renderer_opengl/frame_dumper_opengl.cpp
renderer_opengl/frame_dumper_opengl.h
renderer_opengl/gl_blit_helper.cpp
renderer_opengl/gl_blit_helper.h
renderer_opengl/gl_driver.cpp
renderer_opengl/gl_driver.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp
renderer_opengl/gl_resource_manager.cpp
renderer_opengl/gl_resource_manager.h
renderer_opengl/gl_shader_disk_cache.cpp
renderer_opengl/gl_shader_disk_cache.h
renderer_opengl/gl_shader_manager.cpp
renderer_opengl/gl_shader_manager.h
renderer_opengl/gl_shader_util.cpp
renderer_opengl/gl_shader_util.h
renderer_opengl/gl_state.cpp
renderer_opengl/gl_state.h
renderer_opengl/gl_stream_buffer.cpp
renderer_opengl/gl_stream_buffer.h
renderer_opengl/gl_texture_mailbox.cpp
renderer_opengl/gl_texture_mailbox.h
renderer_opengl/gl_texture_runtime.cpp
renderer_opengl/gl_texture_runtime.h
renderer_opengl/gl_vars.cpp
renderer_opengl/gl_vars.h
renderer_opengl/pica_to_gl.h
renderer_opengl/post_processing_opengl.cpp
renderer_opengl/post_processing_opengl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
renderer_software/renderer_software.cpp
renderer_software/renderer_software.h
# Needed as a fallback regardless of enabled renderers.
renderer_software/sw_blitter.cpp
renderer_software/sw_blitter.h
renderer_software/sw_clipper.cpp
renderer_software/sw_clipper.h
renderer_software/sw_framebuffer.cpp
renderer_software/sw_framebuffer.h
renderer_software/sw_lighting.cpp
renderer_software/sw_lighting.h
renderer_software/sw_proctex.cpp
renderer_software/sw_proctex.h
renderer_software/sw_rasterizer.cpp
renderer_software/sw_rasterizer.h
renderer_software/sw_texturing.cpp
renderer_software/sw_texturing.h
renderer_vulkan/pica_to_vk.h
renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/renderer_vulkan.h
renderer_vulkan/vk_blit_helper.cpp
renderer_vulkan/vk_blit_helper.h
renderer_vulkan/vk_common.cpp
renderer_vulkan/vk_common.h
renderer_vulkan/vk_descriptor_pool.cpp
renderer_vulkan/vk_descriptor_pool.h
renderer_vulkan/vk_graphics_pipeline.cpp
renderer_vulkan/vk_graphics_pipeline.h
renderer_vulkan/vk_master_semaphore.cpp
renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_memory_util.cpp
renderer_vulkan/vk_memory_util.h
renderer_vulkan/vk_rasterizer.cpp
renderer_vulkan/vk_rasterizer.h
renderer_vulkan/vk_rasterizer_cache.cpp
renderer_vulkan/vk_scheduler.cpp
renderer_vulkan/vk_scheduler.h
renderer_vulkan/vk_resource_pool.cpp
renderer_vulkan/vk_resource_pool.h
renderer_vulkan/vk_instance.cpp
renderer_vulkan/vk_instance.h
renderer_vulkan/vk_pipeline_cache.cpp
renderer_vulkan/vk_pipeline_cache.h
renderer_vulkan/vk_platform.cpp
renderer_vulkan/vk_platform.h
renderer_vulkan/vk_present_window.cpp
renderer_vulkan/vk_present_window.h
renderer_vulkan/vk_renderpass_cache.cpp
renderer_vulkan/vk_renderpass_cache.h
renderer_vulkan/vk_shader_util.cpp
renderer_vulkan/vk_shader_util.h
renderer_vulkan/vk_stream_buffer.cpp
renderer_vulkan/vk_stream_buffer.h
renderer_vulkan/vk_swapchain.cpp
renderer_vulkan/vk_swapchain.h
renderer_vulkan/vk_texture_runtime.cpp
renderer_vulkan/vk_texture_runtime.h
shader/debug_data.h
shader/generator/glsl_fs_shader_gen.cpp
shader/generator/glsl_fs_shader_gen.h
@ -162,8 +77,6 @@ add_library(video_core STATIC
shader/generator/shader_gen.h
shader/generator/shader_uniforms.cpp
shader/generator/shader_uniforms.h
shader/generator/spv_fs_shader_gen.cpp
shader/generator/spv_fs_shader_gen.h
shader/shader.cpp
shader/shader.h
shader/shader_interpreter.cpp
@ -183,6 +96,109 @@ add_library(video_core STATIC
video_core.h
)
if (ENABLE_SOFTWARE_RENDERER)
target_sources(video_core PRIVATE
renderer_software/renderer_software.cpp
renderer_software/renderer_software.h
renderer_software/sw_clipper.cpp
renderer_software/sw_clipper.h
renderer_software/sw_framebuffer.cpp
renderer_software/sw_framebuffer.h
renderer_software/sw_lighting.cpp
renderer_software/sw_lighting.h
renderer_software/sw_proctex.cpp
renderer_software/sw_proctex.h
renderer_software/sw_rasterizer.cpp
renderer_software/sw_rasterizer.h
renderer_software/sw_texturing.cpp
renderer_software/sw_texturing.h
)
endif()
if (ENABLE_OPENGL)
target_sources(video_core PRIVATE
renderer_opengl/frame_dumper_opengl.cpp
renderer_opengl/frame_dumper_opengl.h
renderer_opengl/gl_blit_helper.cpp
renderer_opengl/gl_blit_helper.h
renderer_opengl/gl_driver.cpp
renderer_opengl/gl_driver.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp
renderer_opengl/gl_resource_manager.cpp
renderer_opengl/gl_resource_manager.h
renderer_opengl/gl_shader_disk_cache.cpp
renderer_opengl/gl_shader_disk_cache.h
renderer_opengl/gl_shader_manager.cpp
renderer_opengl/gl_shader_manager.h
renderer_opengl/gl_shader_util.cpp
renderer_opengl/gl_shader_util.h
renderer_opengl/gl_state.cpp
renderer_opengl/gl_state.h
renderer_opengl/gl_stream_buffer.cpp
renderer_opengl/gl_stream_buffer.h
renderer_opengl/gl_texture_mailbox.cpp
renderer_opengl/gl_texture_mailbox.h
renderer_opengl/gl_texture_runtime.cpp
renderer_opengl/gl_texture_runtime.h
renderer_opengl/gl_vars.cpp
renderer_opengl/gl_vars.h
renderer_opengl/pica_to_gl.h
renderer_opengl/post_processing_opengl.cpp
renderer_opengl/post_processing_opengl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
)
target_link_libraries(video_core PRIVATE glad)
endif()
if (ENABLE_VULKAN)
target_sources(video_core PRIVATE
renderer_vulkan/pica_to_vk.h
renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/renderer_vulkan.h
renderer_vulkan/vk_blit_helper.cpp
renderer_vulkan/vk_blit_helper.h
renderer_vulkan/vk_common.cpp
renderer_vulkan/vk_common.h
renderer_vulkan/vk_descriptor_pool.cpp
renderer_vulkan/vk_descriptor_pool.h
renderer_vulkan/vk_graphics_pipeline.cpp
renderer_vulkan/vk_graphics_pipeline.h
renderer_vulkan/vk_master_semaphore.cpp
renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_memory_util.cpp
renderer_vulkan/vk_memory_util.h
renderer_vulkan/vk_rasterizer.cpp
renderer_vulkan/vk_rasterizer.h
renderer_vulkan/vk_rasterizer_cache.cpp
renderer_vulkan/vk_scheduler.cpp
renderer_vulkan/vk_scheduler.h
renderer_vulkan/vk_resource_pool.cpp
renderer_vulkan/vk_resource_pool.h
renderer_vulkan/vk_instance.cpp
renderer_vulkan/vk_instance.h
renderer_vulkan/vk_pipeline_cache.cpp
renderer_vulkan/vk_pipeline_cache.h
renderer_vulkan/vk_platform.cpp
renderer_vulkan/vk_platform.h
renderer_vulkan/vk_present_window.cpp
renderer_vulkan/vk_present_window.h
renderer_vulkan/vk_renderpass_cache.cpp
renderer_vulkan/vk_renderpass_cache.h
renderer_vulkan/vk_shader_util.cpp
renderer_vulkan/vk_shader_util.h
renderer_vulkan/vk_stream_buffer.cpp
renderer_vulkan/vk_stream_buffer.h
renderer_vulkan/vk_swapchain.cpp
renderer_vulkan/vk_swapchain.h
renderer_vulkan/vk_texture_runtime.cpp
renderer_vulkan/vk_texture_runtime.h
shader/generator/spv_fs_shader_gen.cpp
shader/generator/spv_fs_shader_gen.h
)
target_link_libraries(video_core PRIVATE vulkan-headers vma sirit SPIRV glslang)
endif()
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
@ -190,7 +206,6 @@ create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC citra_common citra_core)
target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx json-headers nihstro-headers tsl::robin_map)
target_link_libraries(video_core PRIVATE vulkan-headers vma glad sirit SPIRV glslang)
if ("x86_64" IN_LIST ARCHITECTURE)
target_link_libraries(video_core PUBLIC xbyak)

View file

@ -256,6 +256,9 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask) {
case PICA_REG_INDEX(vs.bool_uniforms):
vs_setup.WriteUniformBoolReg(regs.internal.vs.bool_uniforms.Value());
if (!regs.internal.pipeline.gs_unit_exclusive_configuration) {
gs_setup.WriteUniformBoolReg(regs.internal.vs.bool_uniforms.Value());
}
break;
case PICA_REG_INDEX(vs.int_uniforms[0]):
@ -264,6 +267,9 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask) {
case PICA_REG_INDEX(vs.int_uniforms[3]): {
const u32 index = (id - PICA_REG_INDEX(vs.int_uniforms[0]));
vs_setup.WriteUniformIntReg(index, regs.internal.vs.GetIntUniform(index));
if (!regs.internal.pipeline.gs_unit_exclusive_configuration) {
gs_setup.WriteUniformIntReg(index, regs.internal.vs.GetIntUniform(index));
}
break;
}
@ -275,7 +281,10 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask) {
case PICA_REG_INDEX(vs.uniform_setup.set_value[5]):
case PICA_REG_INDEX(vs.uniform_setup.set_value[6]):
case PICA_REG_INDEX(vs.uniform_setup.set_value[7]): {
vs_setup.WriteUniformFloatReg(regs.internal.vs, value);
const auto index = vs_setup.WriteUniformFloatReg(regs.internal.vs, value);
if (!regs.internal.pipeline.gs_unit_exclusive_configuration && index) {
gs_setup.uniforms.f[index.value()] = vs_setup.uniforms.f[index.value()];
}
break;
}

View file

@ -27,21 +27,23 @@ void ShaderSetup::WriteUniformIntReg(u32 index, const Common::Vec4<u8> values) {
uniforms.i[index] = values;
}
void ShaderSetup::WriteUniformFloatReg(ShaderRegs& config, u32 value) {
std::optional<u32> ShaderSetup::WriteUniformFloatReg(ShaderRegs& config, u32 value) {
auto& uniform_setup = config.uniform_setup;
const bool is_float32 = uniform_setup.IsFloat32();
if (!uniform_queue.Push(value, is_float32)) {
return;
return std::nullopt;
}
const auto uniform = uniform_queue.Get(is_float32);
if (uniform_setup.index >= uniforms.f.size()) {
LOG_ERROR(HW_GPU, "Invalid float uniform index {}", uniform_setup.index.Value());
return;
return std::nullopt;
}
uniforms.f[uniform_setup.index] = uniform;
uniform_setup.index.Assign(uniform_setup.index + 1);
const u32 index = uniform_setup.index.Value();
uniforms.f[index] = uniform;
uniform_setup.index.Assign(index + 1);
return index;
}
u64 ShaderSetup::GetProgramCodeHash() {

View file

@ -4,6 +4,7 @@
#pragma once
#include <optional>
#include "common/vector_math.h"
#include "video_core/pica/packed_attribute.h"
#include "video_core/pica_types.h"
@ -58,7 +59,7 @@ public:
void WriteUniformIntReg(u32 index, const Common::Vec4<u8> values);
void WriteUniformFloatReg(ShaderRegs& config, u32 value);
std::optional<u32> WriteUniformFloatReg(ShaderRegs& config, u32 value);
u64 GetProgramCodeHash();

View file

@ -5,9 +5,15 @@
#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/gpu.h"
#ifdef ENABLE_OPENGL
#include "video_core/renderer_opengl/renderer_opengl.h"
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
#include "video_core/renderer_software/renderer_software.h"
#endif
#ifdef ENABLE_VULKAN
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#endif
#include "video_core/video_core.h"
namespace VideoCore {
@ -17,15 +23,32 @@ std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
Pica::PicaCore& pica, Core::System& system) {
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
#ifdef ENABLE_SOFTWARE_RENDERER
case Settings::GraphicsAPI::Software:
return std::make_unique<SwRenderer::RendererSoftware>(system, pica, emu_window);
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
return std::make_unique<Vulkan::RendererVulkan>(system, pica, emu_window, secondary_window);
#endif
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
return std::make_unique<OpenGL::RendererOpenGL>(system, pica, emu_window, secondary_window);
#endif
default:
LOG_CRITICAL(Render, "Unknown graphics API {}, using OpenGL", graphics_api);
LOG_CRITICAL(Render,
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
return std::make_unique<OpenGL::RendererOpenGL>(system, pica, emu_window, secondary_window);
#elif ENABLE_VULKAN
return std::make_unique<Vulkan::RendererVulkan>(system, pica, emu_window, secondary_window);
#elif ENABLE_SOFTWARE_RENDERER
return std::make_unique<SwRenderer::RendererSoftware>(system, pica, emu_window);
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
}
}