diff options
119 files changed, 3296 insertions, 3165 deletions
@@ -3,7 +3,7 @@ Citra Emulator [![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg)](https://travis-ci.org/citra-emu/citra) [![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra) -Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! None of these run to a playable state, but we are working every day to advance the project forward. +Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward. Citra is licensed under the GPLv2. Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project. diff --git a/externals/nihstro b/externals/nihstro -Subproject 0a8b4d221425f13e24a3cef9b02edc3221bab21 +Subproject 4a78588b308564f7ebae193e0ae00d9a0d5741d diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 69f0b35b3..2c6ced920 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -22,7 +22,8 @@ int __cdecl main(int argc, char **argv) { std::shared_ptr<Log::Logger> logger = Log::InitGlobalLogger(); Log::Filter log_filter(Log::Level::Debug); - std::thread logging_thread(Log::TextLoggingLoop, logger, &log_filter); + Log::SetFilter(&log_filter); + std::thread logging_thread(Log::TextLoggingLoop, logger); SCOPE_EXIT({ logger->Close(); logging_thread.join(); diff --git a/src/citra/config.cpp b/src/citra/config.cpp index f5b4069c7..e9f3dfd5b 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -44,6 +44,8 @@ void Config::ReadValues() { Settings::values.pad_y_key = glfw_config->GetInteger("Controls", "pad_y", GLFW_KEY_X); Settings::values.pad_l_key = glfw_config->GetInteger("Controls", "pad_l", GLFW_KEY_Q); Settings::values.pad_r_key = glfw_config->GetInteger("Controls", "pad_r", GLFW_KEY_W); + Settings::values.pad_zl_key = glfw_config->GetInteger("Controls", "pad_zl", GLFW_KEY_1); + Settings::values.pad_zr_key = glfw_config->GetInteger("Controls", "pad_zr", GLFW_KEY_2); Settings::values.pad_start_key = glfw_config->GetInteger("Controls", "pad_start", GLFW_KEY_M); Settings::values.pad_select_key = glfw_config->GetInteger("Controls", "pad_select", GLFW_KEY_N); Settings::values.pad_home_key = glfw_config->GetInteger("Controls", "pad_home", GLFW_KEY_B); @@ -55,6 +57,10 @@ void Config::ReadValues() { Settings::values.pad_sdown_key = glfw_config->GetInteger("Controls", "pad_sdown", GLFW_KEY_DOWN); Settings::values.pad_sleft_key = glfw_config->GetInteger("Controls", "pad_sleft", GLFW_KEY_LEFT); Settings::values.pad_sright_key = glfw_config->GetInteger("Controls", "pad_sright", GLFW_KEY_RIGHT); + Settings::values.pad_cup_key = glfw_config->GetInteger("Controls", "pad_cup", GLFW_KEY_I); + Settings::values.pad_cdown_key = glfw_config->GetInteger("Controls", "pad_cdown", GLFW_KEY_K); + Settings::values.pad_cleft_key = glfw_config->GetInteger("Controls", "pad_cleft", GLFW_KEY_J); + Settings::values.pad_cright_key = glfw_config->GetInteger("Controls", "pad_cright", GLFW_KEY_L); // Core Settings::values.gpu_refresh_rate = glfw_config->GetInteger("Core", "gpu_refresh_rate", 30); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index be4b289bd..fc02a3ceb 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -19,25 +19,42 @@ pad_a = pad_b = pad_x = pad_y = -pad_r = pad_l = +pad_r = +pad_zl = +pad_zr = pad_sup = pad_sdown = pad_sleft = pad_sright = +pad_cup = +pad_cdown = +pad_cleft = +pad_cright = [Core] -gpu_refresh_rate = ## 30 (default) -frame_skip = ## 0: No frameskip (default), 1 : 2x frameskip, 2 : 4x frameskip, etc. +# The refresh rate for the GPU +# Defaults to 30 +gpu_refresh_rate = + +# The applied frameskip amount. Must be a power of two. +# 0 (default): No frameskip, 1: x2 frameskip, 2: x4 frameskip, 3: x8 frameskip, etc. +frame_skip = [Data Storage] +# Whether to create a virtual SD card. +# 1 (default): Yes, 0: No use_virtual_sd = [System Region] -region_value = ## 0 : Japan, 1 : Usa (default), 2 : Europe, 3 : Australia, 4 : China, 5 : Korea, 6 : Taiwan. +# The system region that Citra will use during emulation +# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_value = [Miscellaneous] -log_filter = *:Info ## Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Info )"; } diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp index 8a3ee64a8..81231e1e5 100644 --- a/src/citra/emu_window/emu_window_glfw.cpp +++ b/src/citra/emu_window/emu_window_glfw.cpp @@ -36,20 +36,13 @@ const bool EmuWindow_GLFW::IsOpen() { } void EmuWindow_GLFW::OnFramebufferResizeEvent(GLFWwindow* win, int width, int height) { - ASSERT(width > 0); - ASSERT(height > 0); - - GetEmuWindow(win)->NotifyFramebufferSizeChanged(std::pair<unsigned,unsigned>(width, height)); + GetEmuWindow(win)->NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height)); } void EmuWindow_GLFW::OnClientAreaResizeEvent(GLFWwindow* win, int width, int height) { - ASSERT(width > 0); - ASSERT(height > 0); - // NOTE: GLFW provides no proper way to set a minimal window size. // Hence, we just ignore the corresponding EmuWindow hint. - - GetEmuWindow(win)->NotifyClientAreaSizeChanged(std::pair<unsigned,unsigned>(width, height)); + OnFramebufferResizeEvent(win, width, height); } /// EmuWindow_GLFW constructor @@ -139,6 +132,16 @@ void EmuWindow_GLFW::ReloadSetKeymaps() { KeyMap::SetKeyMapping({Settings::values.pad_l_key, keyboard_id}, Service::HID::PAD_L); KeyMap::SetKeyMapping({Settings::values.pad_x_key, keyboard_id}, Service::HID::PAD_X); KeyMap::SetKeyMapping({Settings::values.pad_y_key, keyboard_id}, Service::HID::PAD_Y); + + KeyMap::SetKeyMapping({Settings::values.pad_zl_key, keyboard_id}, Service::HID::PAD_ZL); + KeyMap::SetKeyMapping({Settings::values.pad_zr_key, keyboard_id}, Service::HID::PAD_ZR); + + // KeyMap::SetKeyMapping({Settings::values.pad_touch_key, keyboard_id}, Service::HID::PAD_TOUCH); + + KeyMap::SetKeyMapping({Settings::values.pad_cright_key, keyboard_id}, Service::HID::PAD_C_RIGHT); + KeyMap::SetKeyMapping({Settings::values.pad_cleft_key, keyboard_id}, Service::HID::PAD_C_LEFT); + KeyMap::SetKeyMapping({Settings::values.pad_cup_key, keyboard_id}, Service::HID::PAD_C_UP); + KeyMap::SetKeyMapping({Settings::values.pad_cdown_key, keyboard_id}, Service::HID::PAD_C_DOWN); KeyMap::SetKeyMapping({Settings::values.pad_sright_key, keyboard_id}, Service::HID::PAD_CIRCLE_RIGHT); KeyMap::SetKeyMapping({Settings::values.pad_sleft_key, keyboard_id}, Service::HID::PAD_CIRCLE_LEFT); KeyMap::SetKeyMapping({Settings::values.pad_sup_key, keyboard_id}, Service::HID::PAD_CIRCLE_UP); diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 586bc84b0..ff780cad4 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRCS debugger/graphics_cmdlists.cpp debugger/graphics_framebuffer.cpp debugger/graphics_vertex_shader.cpp + debugger/profiler.cpp debugger/ramview.cpp debugger/registers.cpp util/spinbox.cpp @@ -35,6 +36,7 @@ set(HEADERS debugger/graphics_cmdlists.h debugger/graphics_framebuffer.h debugger/graphics_vertex_shader.h + debugger/profiler.h debugger/ramview.h debugger/registers.h util/spinbox.h @@ -48,6 +50,7 @@ set(UIS config/controller_config.ui debugger/callstack.ui debugger/disassembler.ui + debugger/profiler.ui debugger/registers.ui hotkeys.ui main.ui @@ -61,7 +64,11 @@ else() qt4_wrap_ui(UI_HDRS ${UIS}) endif() -add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) +if (APPLE) + add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS}) +else() + add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) +endif() target_link_libraries(citra-qt core common video_core qhexedit) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 22a7842bf..a040e75c1 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -155,6 +155,7 @@ GRenderWindow::GRenderWindow(QWidget* parent) : QWidget(parent), emu_thread(this child = new GGLWidgetInternal(fmt, this); QBoxLayout* layout = new QHBoxLayout(this); + resize(VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight); layout->addWidget(child); layout->setMargin(0); @@ -234,7 +235,7 @@ void GRenderWindow::OnFramebufferSizeChanged() unsigned height = child->QPaintDevice::height(); #endif - NotifyFramebufferSizeChanged(std::make_pair(width, height)); + NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height)); } void GRenderWindow::BackupGeometry() @@ -291,6 +292,16 @@ void GRenderWindow::ReloadSetKeymaps() KeyMap::SetKeyMapping({Settings::values.pad_l_key, keyboard_id}, Service::HID::PAD_L); KeyMap::SetKeyMapping({Settings::values.pad_x_key, keyboard_id}, Service::HID::PAD_X); KeyMap::SetKeyMapping({Settings::values.pad_y_key, keyboard_id}, Service::HID::PAD_Y); + + KeyMap::SetKeyMapping({Settings::values.pad_zl_key, keyboard_id}, Service::HID::PAD_ZL); + KeyMap::SetKeyMapping({Settings::values.pad_zr_key, keyboard_id}, Service::HID::PAD_ZR); + + // KeyMap::SetKeyMapping({Settings::values.pad_touch_key, keyboard_id}, Service::HID::PAD_TOUCH); + + KeyMap::SetKeyMapping({Settings::values.pad_cright_key, keyboard_id}, Service::HID::PAD_C_RIGHT); + KeyMap::SetKeyMapping({Settings::values.pad_cleft_key, keyboard_id}, Service::HID::PAD_C_LEFT); + KeyMap::SetKeyMapping({Settings::values.pad_cup_key, keyboard_id}, Service::HID::PAD_C_UP); + KeyMap::SetKeyMapping({Settings::values.pad_cdown_key, keyboard_id}, Service::HID::PAD_C_DOWN); KeyMap::SetKeyMapping({Settings::values.pad_sright_key, keyboard_id}, Service::HID::PAD_CIRCLE_RIGHT); KeyMap::SetKeyMapping({Settings::values.pad_sleft_key, keyboard_id}, Service::HID::PAD_CIRCLE_LEFT); KeyMap::SetKeyMapping({Settings::values.pad_sup_key, keyboard_id}, Service::HID::PAD_CIRCLE_UP); diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 76aeaedd0..ac250e0a5 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -23,12 +23,14 @@ Config::Config() { void Config::ReadValues() { qt_config->beginGroup("Controls"); - Settings::values.pad_a_key = qt_config->value("pad_a", Qt::Key_A).toInt(); - Settings::values.pad_b_key = qt_config->value("pad_b", Qt::Key_S).toInt(); - Settings::values.pad_x_key = qt_config->value("pad_x", Qt::Key_Z).toInt(); - Settings::values.pad_y_key = qt_config->value("pad_y", Qt::Key_X).toInt(); - Settings::values.pad_l_key = qt_config->value("pad_l", Qt::Key_Q).toInt(); - Settings::values.pad_r_key = qt_config->value("pad_r", Qt::Key_W).toInt(); + Settings::values.pad_a_key = qt_config->value("pad_a", Qt::Key_A).toInt(); + Settings::values.pad_b_key = qt_config->value("pad_b", Qt::Key_S).toInt(); + Settings::values.pad_x_key = qt_config->value("pad_x", Qt::Key_Z).toInt(); + Settings::values.pad_y_key = qt_config->value("pad_y", Qt::Key_X).toInt(); + Settings::values.pad_l_key = qt_config->value("pad_l", Qt::Key_Q).toInt(); + Settings::values.pad_r_key = qt_config->value("pad_r", Qt::Key_W).toInt(); + Settings::values.pad_zl_key = qt_config->value("pad_zl", Qt::Key_1).toInt(); + Settings::values.pad_zr_key = qt_config->value("pad_zr", Qt::Key_2).toInt(); Settings::values.pad_start_key = qt_config->value("pad_start", Qt::Key_M).toInt(); Settings::values.pad_select_key = qt_config->value("pad_select", Qt::Key_N).toInt(); Settings::values.pad_home_key = qt_config->value("pad_home", Qt::Key_B).toInt(); @@ -40,6 +42,10 @@ void Config::ReadValues() { Settings::values.pad_sdown_key = qt_config->value("pad_sdown", Qt::Key_Down).toInt(); Settings::values.pad_sleft_key = qt_config->value("pad_sleft", Qt::Key_Left).toInt(); Settings::values.pad_sright_key = qt_config->value("pad_sright", Qt::Key_Right).toInt(); + Settings::values.pad_cup_key = qt_config->value("pad_cup", Qt::Key_I).toInt(); + Settings::values.pad_cdown_key = qt_config->value("pad_cdown", Qt::Key_K).toInt(); + Settings::values.pad_cleft_key = qt_config->value("pad_cleft", Qt::Key_J).toInt(); + Settings::values.pad_cright_key = qt_config->value("pad_cright", Qt::Key_L).toInt(); qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -62,12 +68,14 @@ void Config::ReadValues() { void Config::SaveValues() { qt_config->beginGroup("Controls"); - qt_config->setValue("pad_a", Settings::values.pad_a_key); - qt_config->setValue("pad_b", Settings::values.pad_b_key); - qt_config->setValue("pad_x", Settings::values.pad_x_key); - qt_config->setValue("pad_y", Settings::values.pad_y_key); - qt_config->setValue("pad_l", Settings::values.pad_l_key); - qt_config->setValue("pad_r", Settings::values.pad_r_key); + qt_config->setValue("pad_a", Settings::values.pad_a_key); + qt_config->setValue("pad_b", Settings::values.pad_b_key); + qt_config->setValue("pad_x", Settings::values.pad_x_key); + qt_config->setValue("pad_y", Settings::values.pad_y_key); + qt_config->setValue("pad_l", Settings::values.pad_l_key); + qt_config->setValue("pad_r", Settings::values.pad_r_key); + qt_config->setValue("pad_zl", Settings::values.pad_zl_key); + qt_config->setValue("pad_zr", Settings::values.pad_zr_key); qt_config->setValue("pad_start", Settings::values.pad_start_key); qt_config->setValue("pad_select", Settings::values.pad_select_key); qt_config->setValue("pad_home", Settings::values.pad_home_key); @@ -79,6 +87,10 @@ void Config::SaveValues() { qt_config->setValue("pad_sdown", Settings::values.pad_sdown_key); qt_config->setValue("pad_sleft", Settings::values.pad_sleft_key); qt_config->setValue("pad_sright", Settings::values.pad_sright_key); + qt_config->setValue("pad_cup", Settings::values.pad_cup_key); + qt_config->setValue("pad_cdown", Settings::values.pad_cdown_key); + qt_config->setValue("pad_cleft", Settings::values.pad_cleft_key); + qt_config->setValue("pad_cright", Settings::values.pad_cright_key); qt_config->endGroup(); qt_config->beginGroup("Core"); diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index bd420f24a..9bcd25821 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -66,7 +66,7 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo QComboBox* format_choice = new QComboBox; format_choice->addItem(tr("RGBA8")); format_choice->addItem(tr("RGB8")); - format_choice->addItem(tr("RGBA5551")); + format_choice->addItem(tr("RGB5A1")); format_choice->addItem(tr("RGB565")); format_choice->addItem(tr("RGBA4")); format_choice->addItem(tr("IA8")); diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index 1ba60021f..5bd6c0235 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -9,8 +9,10 @@ #include <QPushButton> #include <QSpinBox> +#include "core/hw/gpu.h" #include "video_core/color.h" #include "video_core/pica.h" +#include "video_core/utils.h" #include "graphics_framebuffer.h" @@ -44,7 +46,7 @@ GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::Debug framebuffer_format_control = new QComboBox; framebuffer_format_control->addItem(tr("RGBA8")); framebuffer_format_control->addItem(tr("RGB8")); - framebuffer_format_control->addItem(tr("RGBA5551")); + framebuffer_format_control->addItem(tr("RGB5A1")); framebuffer_format_control->addItem(tr("RGB565")); framebuffer_format_control->addItem(tr("RGBA4")); @@ -195,60 +197,42 @@ void GraphicsFramebufferWidget::OnUpdate() // TODO: Implement a good way to visualize alpha components! // TODO: Unify this decoding code with the texture decoder - switch (framebuffer_format) { - case Format::RGBA8: - { - QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); - for (unsigned int y = 0; y < framebuffer_height; ++y) { - for (unsigned int x = 0; x < framebuffer_width; ++x) { - u32 value = *(color_buffer + x + y * framebuffer_width); - - decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); + u32 bytes_per_pixel = GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer_format)); + + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + for (unsigned int y = 0; y < framebuffer_height; ++y) { + for (unsigned int x = 0; x < framebuffer_width; ++x) { + const u32 coarse_y = y & ~7; + u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel; + const u8* pixel = color_buffer + offset; + Math::Vec4<u8> color = { 0, 0, 0, 0 }; + + switch (framebuffer_format) { + case Format::RGBA8: + color = Color::DecodeRGBA8(pixel); + break; + case Format::RGB8: + color = Color::DecodeRGB8(pixel); + break; + case Format::RGB5A1: + color = Color::DecodeRGB5A1(pixel); + break; + case Format::RGB565: + color = Color::DecodeRGB565(pixel); + break; + case Format::RGBA4: + color = Color::DecodeRGBA4(pixel); + break; + default: + qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); + break; } - } - pixmap = QPixmap::fromImage(decoded_image); - break; - } - case Format::RGB8: - { - QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); - for (unsigned int y = 0; y < framebuffer_height; ++y) { - for (unsigned int x = 0; x < framebuffer_width; ++x) { - u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; - - decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); - } + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255)); } - pixmap = QPixmap::fromImage(decoded_image); - break; - } - - case Format::RGBA5551: - { - QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); - for (unsigned int y = 0; y < framebuffer_height; ++y) { - for (unsigned int x = 0; x < framebuffer_width; ++x) { - u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); - u8 r = Color::Convert5To8((value >> 11) & 0x1F); - u8 g = Color::Convert5To8((value >> 6) & 0x1F); - u8 b = Color::Convert5To8((value >> 1) & 0x1F); - u8 a = Color::Convert1To8(value & 1); - - decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); - } - } - pixmap = QPixmap::fromImage(decoded_image); - break; - } - - default: - qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); - break; } + pixmap = QPixmap::fromImage(decoded_image); framebuffer_address_control->SetValue(framebuffer_address); framebuffer_width_control->setValue(framebuffer_width); diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h index c6e293bc9..15ebd1f7d 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.h +++ b/src/citra_qt/debugger/graphics_framebuffer.h @@ -29,7 +29,7 @@ class GraphicsFramebufferWidget : public BreakPointObserverDock { enum class Format { RGBA8 = 0, RGB8 = 1, - RGBA5551 = 2, + RGB5A1 = 2, RGB565 = 3, RGBA4 = 4, }; diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index 06eaf0bf0..3b072d015 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -12,6 +12,7 @@ #include "graphics_vertex_shader.h" +using nihstro::OpCode; using nihstro::Instruction; using nihstro::SourceRegister; using nihstro::SwizzlePattern; @@ -78,7 +79,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con const SwizzlePattern& swizzle = info.swizzle_info[instr.common.operand_desc_id].pattern; // longest known instruction name: "setemit " - output << std::setw(8) << std::left << instr.opcode.GetInfo().name; + output << std::setw(8) << std::left << instr.opcode.Value().GetInfo().name; // e.g. "-c92.xyzw" static auto print_input = [](std::stringstream& output, const SourceRegister& input, @@ -109,16 +110,16 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con print_input_indexed(output, input, negate, swizzle_mask, address_register_name); }; - switch (instr.opcode.GetInfo().type) { - case Instruction::OpCodeType::Trivial: + switch (instr.opcode.Value().GetInfo().type) { + case OpCode::Type::Trivial: // Nothing to do here break; - case Instruction::OpCodeType::Arithmetic: + case OpCode::Type::Arithmetic: { // Use custom code for special instructions - switch (instr.opcode.EffectiveOpCode()) { - case Instruction::OpCode::CMP: + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::CMP: { // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. output << std::setw(4) << std::right << "cc."; @@ -142,13 +143,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con default: { - bool src_is_inverted = 0 != (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::SrcInversed); + bool src_is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed); - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Dest) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Dest) { // e.g. "r12.xy__" - output << std::setw(4) << std::right << instr.common.dest.GetName() + "."; + output << std::setw(4) << std::right << instr.common.dest.Value().GetName() + "."; output << swizzle.DestMaskToString(); - } else if (instr.opcode.GetInfo().subtype == Instruction::OpCodeInfo::MOVA) { + } else if (instr.opcode.Value().GetInfo().subtype == OpCode::Info::MOVA) { output << std::setw(4) << std::right << "a0."; output << swizzle.DestMaskToString(); } else { @@ -156,7 +157,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } output << " "; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src1) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src1) { SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); print_input_indexed(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), instr.common.AddressRegisterName()); } else { @@ -164,7 +165,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src2) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src2) { SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(false)); } @@ -175,17 +176,17 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con break; } - case Instruction::OpCodeType::Conditional: + case OpCode::Type::Conditional: { - switch (instr.opcode.EffectiveOpCode()) { - case Instruction::OpCode::LOOP: + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::LOOP: output << "(unknown instruction format)"; break; default: output << "if "; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasCondition) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasCondition) { const char* ops[] = { " || ", " && ", "", "" }; @@ -198,22 +199,22 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con output << ((!instr.flow_control.refy) ? "!" : " ") << "cc.y"; output << " "; - } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasUniformIndex) { + } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasUniformIndex) { output << "b" << instr.flow_control.bool_uniform_id << " "; } u32 target_addr = instr.flow_control.dest_offset; u32 target_addr_else = instr.flow_control.dest_offset; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasAlternative) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasAlternative) { output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; - } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasExplicitDest) { + } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasExplicitDest) { output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; } else { // TODO: Handle other cases } - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasFinishPoint) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasFinishPoint) { output << "(return on " << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions << ")"; } diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp new file mode 100644 index 000000000..ae0568b6a --- /dev/null +++ b/src/citra_qt/debugger/profiler.cpp @@ -0,0 +1,138 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "profiler.h" + +#include "common/profiler_reporting.h" + +using namespace Common::Profiling; + +static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) +{ + static auto duration_to_float = [](Duration dur) -> float { + using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; + return std::chrono::duration_cast<FloatMs>(dur).count(); + }; + + switch (col) { + case 1: return duration_to_float(duration.avg); + case 2: return duration_to_float(duration.min); + case 3: return duration_to_float(duration.max); + default: return QVariant(); + } +} + +static const TimingCategoryInfo* GetCategoryInfo(int id) +{ + const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); + if (id >= categories.size()) { + return nullptr; + } else { + return &categories[id]; + } +} + +ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) +{ + updateProfilingInfo(); + const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); + results.time_per_category.resize(categories.size()); +} + +QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case 0: return tr("Category"); + case 1: return tr("Avg"); + case 2: return tr("Min"); + case 3: return tr("Max"); + } + } + + return QVariant(); +} + +QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const +{ + return createIndex(row, column); +} + +QModelIndex ProfilerModel::parent(const QModelIndex& child) const +{ + return QModelIndex(); +} + +int ProfilerModel::columnCount(const QModelIndex& parent) const +{ + return 4; +} + +int ProfilerModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return results.time_per_category.size() + 2; + } +} + +QVariant ProfilerModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::DisplayRole) { + if (index.row() == 0) { + if (index.column() == 0) { + return tr("Frame"); + } else { + return GetDataForColumn(index.column(), results.frame_time); + } + } else if (index.row() == 1) { + if (index.column() == 0) { + return tr("Frame (with swapping)"); + } else { + return GetDataForColumn(index.column(), results.interframe_time); + } + } else { + if (index.column() == 0) { + const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); + return info != nullptr ? QString(info->name) : QVariant(); + } else { + if (index.row() - 2 < results.time_per_category.size()) { + return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); + } else { + return QVariant(); + } + } + } + } + + return QVariant(); +} + +void ProfilerModel::updateProfilingInfo() +{ + results = GetTimingResultsAggregator()->GetAggregatedResults(); + emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); +} + +ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) +{ + ui.setupUi(this); + + model = new ProfilerModel(this); + ui.treeView->setModel(model); + + connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); + connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); +} + +void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) +{ + if (enable) { + update_timer.start(100); + model->updateProfilingInfo(); + } else { + update_timer.stop(); + } +} diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h new file mode 100644 index 000000000..a6d87aa0f --- /dev/null +++ b/src/citra_qt/debugger/profiler.h @@ -0,0 +1,50 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QAbstractItemModel> +#include <QDockWidget> +#include <QTimer> +#include "ui_profiler.h" + +#include "common/profiler_reporting.h" + +class ProfilerModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + ProfilerModel(QObject* parent); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + +public slots: + void updateProfilingInfo(); + +private: + Common::Profiling::AggregatedFrameResult results; +}; + +class ProfilerWidget : public QDockWidget +{ + Q_OBJECT + +public: + ProfilerWidget(QWidget* parent = 0); + +private slots: + void setProfilingInfoUpdateEnabled(bool enable); + +private: + Ui::Profiler ui; + ProfilerModel* model; + + QTimer update_timer; +}; diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui new file mode 100644 index 000000000..d3c9a9a1f --- /dev/null +++ b/src/citra_qt/debugger/profiler.ui @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Profiler</class> + <widget class="QDockWidget" name="Profiler"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Profiler</string> + </property> + <widget class="QWidget" name="dockWidgetContents"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeView" name="treeView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 881c7d337..e5ca04124 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -35,6 +35,7 @@ #include "debugger/graphics_cmdlists.h" #include "debugger/graphics_framebuffer.h" #include "debugger/graphics_vertex_shader.h" +#include "debugger/profiler.h" #include "core/settings.h" #include "core/system.h" @@ -57,6 +58,10 @@ GMainWindow::GMainWindow() render_window = new GRenderWindow; render_window->hide(); + profilerWidget = new ProfilerWidget(this); + addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); + profilerWidget->hide(); + disasmWidget = new DisassemblerWidget(this, render_window->GetEmuThread()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); disasmWidget->hide(); @@ -90,6 +95,7 @@ GMainWindow::GMainWindow() graphicsVertexShaderWidget->hide(); QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); + debug_menu->addAction(profilerWidget->toggleViewAction()); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); debug_menu->addAction(callstackWidget->toggleViewAction()); @@ -310,7 +316,8 @@ int __cdecl main(int argc, char* argv[]) { std::shared_ptr<Log::Logger> logger = Log::InitGlobalLogger(); Log::Filter log_filter(Log::Level::Info); - std::thread logging_thread(Log::TextLoggingLoop, logger, &log_filter); + Log::SetFilter(&log_filter); + std::thread logging_thread(Log::TextLoggingLoop, logger); SCOPE_EXIT({ logger->Close(); logging_thread.join(); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index dd53489dd..9b57c5772 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -11,6 +11,7 @@ class GImageInfo; class GRenderWindow; +class ProfilerWidget; class DisassemblerWidget; class RegistersWidget; class CallstackWidget; @@ -54,6 +55,7 @@ private: GRenderWindow* render_window; + ProfilerWidget* profilerWidget; DisassemblerWidget* disasmWidget; RegistersWidget* registersWidget; CallstackWidget* callstackWidget; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 8c87deaa4..daa2d59de 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -4,8 +4,6 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU set(SRCS break_points.cpp emu_window.cpp - extended_trace.cpp - file_search.cpp file_util.cpp hash.cpp key_map.cpp @@ -16,13 +14,12 @@ set(SRCS mem_arena.cpp memory_util.cpp misc.cpp - msg_handler.cpp + profiler.cpp scm_rev.cpp string_util.cpp symbols.cpp thread.cpp timer.cpp - utf8.cpp ) set(HEADERS @@ -38,9 +35,7 @@ set(HEADERS cpu_detect.h debug_interface.h emu_window.h - extended_trace.h fifo_queue.h - file_search.h file_util.h hash.h key_map.h @@ -53,18 +48,19 @@ set(HEADERS math_util.h mem_arena.h memory_util.h - msg_handler.h platform.h + profiler.h + profiler_reporting.h scm_rev.h scope_exit.h string_util.h swap.h symbols.h + synchronized_wrapper.h thread.h thread_queue_list.h thunk.h timer.h - utf8.h ) create_directory_groups(${SRCS} ${HEADERS}) diff --git a/src/common/assert.h b/src/common/assert.h index 3b2232a7e..9ca7adb15 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -4,24 +4,43 @@ #pragma once +#include <cstdlib> + #include "common/common_funcs.h" +// For asserts we'd like to keep all the junk executed when an assert happens away from the +// important code in the function. One way of doing this is to put all the relevant code inside a +// lambda and force the compiler to not inline it. Unfortunately, MSVC seems to have no syntax to +// specify __declspec on lambda functions, so what we do instead is define a noinline wrapper +// template that calls the lambda. This seems to generate an extra instruction at the call-site +// compared to the ideal implementation (which wouldn't support ASSERT_MSG parameters), but is good +// enough for our purposes. +template <typename Fn> +#if defined(_MSC_VER) + __declspec(noinline, noreturn) +#elif defined(__GNUC__) + __attribute__((noinline, noreturn, cold)) +#endif +static void assert_noinline_call(const Fn& fn) { + fn(); + Crash(); + exit(1); // Keeps GCC's mouth shut about this actually returning +} + // TODO (yuriks) allow synchronous logging so we don't need printf #define ASSERT(_a_) \ - do if (!(_a_)) {\ + do if (!(_a_)) { assert_noinline_call([] { \ fprintf(stderr, "Assertion Failed!\n\n Line: %d\n File: %s\n Time: %s\n", \ __LINE__, __FILE__, __TIME__); \ - Crash(); \ - } while (0) + }); } while (0) #define ASSERT_MSG(_a_, ...) \ - do if (!(_a_)) {\ + do if (!(_a_)) { assert_noinline_call([&] { \ fprintf(stderr, "Assertion Failed!\n\n Line: %d\n File: %s\n Time: %s\n", \ __LINE__, __FILE__, __TIME__); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ - Crash(); \ - } while (0) + }); } while (0) #define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h index dc27da088..3f97d56bf 100644 --- a/src/common/chunk_file.h +++ b/src/common/chunk_file.h @@ -637,7 +637,7 @@ public: Do(cookie); if(mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { - PanicAlertT("Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); + LOG_ERROR(Common, "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); SetError(ERROR_FAILURE); } } diff --git a/src/common/common.h b/src/common/common.h index ad2de6f2e..f7d0f55c5 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -28,7 +28,6 @@ private: #include "common/assert.h" #include "common/logging/log.h" #include "common/common_types.h" -#include "common/msg_handler.h" #include "common/common_funcs.h" #include "common/common_paths.h" #include "common/platform.h" @@ -36,13 +35,11 @@ private: #ifdef __APPLE__ // The Darwin ABI requires that stack frames be aligned to 16-byte boundaries. // This is only needed on i386 gcc - x86_64 already aligns to 16 bytes. -#if defined __i386__ && defined __GNUC__ -#undef STACKALIGN -#define STACKALIGN __attribute__((__force_align_arg_pointer__)) -#endif - + #if defined __i386__ && defined __GNUC__ + #undef STACKALIGN + #define STACKALIGN __attribute__((__force_align_arg_pointer__)) + #endif #elif defined _WIN32 - // Check MSC ver #if defined _MSC_VER && _MSC_VER <= 1000 #error needs at least version 1000 of MSC @@ -52,9 +49,6 @@ private: #define NOMINMAX #endif -// Memory leak checks - #define CHECK_HEAP_INTEGRITY() - // Alignment #define MEMORY_ALIGNED16(x) __declspec(align(16)) x #define MEMORY_ALIGNED32(x) __declspec(align(32)) x @@ -62,57 +56,34 @@ private: #define MEMORY_ALIGNED128(x) __declspec(align(128)) x #define MEMORY_ALIGNED16_DECL(x) __declspec(align(16)) x #define MEMORY_ALIGNED64_DECL(x) __declspec(align(64)) x - -// Since they are always around on windows - #define HAVE_WX 1 - #define HAVE_OPENAL 1 - - #define HAVE_PORTAUDIO 1 - -// Debug definitions - #if defined(_DEBUG) - #include <crtdbg.h> - #undef CHECK_HEAP_INTEGRITY - #define CHECK_HEAP_INTEGRITY() {if (!_CrtCheckMemory()) PanicAlert("memory corruption detected. see log.");} - // If you want to see how much a pain in the ass singletons are, for example: - // {614} normal block at 0x030C5310, 188 bytes long. - // Data: <Master Log > 4D 61 73 74 65 72 20 4C 6F 67 00 00 00 00 00 00 - struct CrtDebugBreak { CrtDebugBreak(int spot) { _CrtSetBreakAlloc(spot); } }; - //CrtDebugBreak breakAt(614); - #endif // end DEBUG/FAST - #endif // Windows compatibility #ifndef _WIN32 -#ifdef _LP64 -#define _M_X64 1 -#else -#define _M_IX86 1 -#endif -#define __forceinline inline __attribute__((always_inline)) -#define MEMORY_ALIGNED16(x) __attribute__((aligned(16))) x -#define MEMORY_ALIGNED32(x) __attribute__((aligned(32))) x -#define MEMORY_ALIGNED64(x) __attribute__((aligned(64))) x -#define MEMORY_ALIGNED128(x) __attribute__((aligned(128))) x -#define MEMORY_ALIGNED16_DECL(x) __attribute__((aligned(16))) x -#define MEMORY_ALIGNED64_DECL(x) __attribute__((aligned(64))) x + #ifdef _LP64 + #define _M_X64 1 + #else + #define _M_IX86 1 + #endif + #define __forceinline inline __attribute__((always_inline)) + #define MEMORY_ALIGNED16(x) __attribute__((aligned(16))) x + #define MEMORY_ALIGNED32(x) __attribute__((aligned(32))) x + #define MEMORY_ALIGNED64(x) __attribute__((aligned(64))) x + #define MEMORY_ALIGNED128(x) __attribute__((aligned(128))) x + #define MEMORY_ALIGNED16_DECL(x) __attribute__((aligned(16))) x + #define MEMORY_ALIGNED64_DECL(x) __attribute__((aligned(64))) x #endif #ifdef _MSC_VER -#define __strdup _strdup -#define __getcwd _getcwd -#define __chdir _chdir + #define __strdup _strdup + #define __getcwd _getcwd + #define __chdir _chdir #else -#define __strdup strdup -#define __getcwd getcwd -#define __chdir chdir + #define __strdup strdup + #define __getcwd getcwd + #define __chdir chdir #endif -// Dummy macro for marking translatable strings that can not be immediately translated. -// wxWidgets does not have a true dummy macro for this. -#define _trans(a) a - #if defined _M_GENERIC # define _M_SSE 0x0 #elif defined __GNUC__ @@ -146,40 +117,4 @@ enum EMUSTATE_CHANGE EMUSTATE_CHANGE_STOP }; - -#ifdef _MSC_VER -inline unsigned long long bswap64(unsigned long long x) { return _byteswap_uint64(x); } -inline unsigned int bswap32(unsigned int x) { return _byteswap_ulong(x); } -inline unsigned short bswap16(unsigned short x) { return _byteswap_ushort(x); } -#else -// TODO: speedup -inline unsigned short bswap16(unsigned short x) { return (x << 8) | (x >> 8); } -inline unsigned int bswap32(unsigned int x) { return (x >> 24) | ((x & 0xFF0000) >> 8) | ((x & 0xFF00) << 8) | (x << 24);} -inline unsigned long long bswap64(unsigned long long x) {return ((unsigned long long)bswap32(x) << 32) | bswap32(x >> 32); } -#endif - -inline float bswapf(float f) { - union { - float f; - unsigned int u32; - } dat1, dat2; - - dat1.f = f; - dat2.u32 = bswap32(dat1.u32); - - return dat2.f; -} - -inline double bswapd(double f) { - union { - double f; - unsigned long long u64; - } dat1, dat2; - - dat1.f = f; - dat2.u64 = bswap64(dat1.u64); - - return dat2.f; -} - #include "swap.h" diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 4bbcc3571..e76cb7d68 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -14,8 +14,6 @@ #define SLEEP(x) usleep(x*1000) #endif -template <bool> struct CompileTimeAssert; -template<> struct CompileTimeAssert<true> {}; #define b2(x) ( (x) | ( (x) >> 1) ) #define b4(x) ( b2(x) | ( b2(x) >> 2) ) @@ -24,11 +22,6 @@ template<> struct CompileTimeAssert<true> {}; #define b32(x) (b16(x) | (b16(x) >>16) ) #define ROUND_UP_POW2(x) (b32(x - 1) + 1) -#define MIN(a, b) ((a)<(b)?(a):(b)) -#define MAX(a, b) ((a)>(b)?(a):(b)) - -#define CLAMP(x, min, max) (((x) > max) ? max : (((x) < min) ? min : (x))) - #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) /// Textually concatenates two tokens. The double-expansion is required by the C preprocessor. @@ -38,18 +31,12 @@ template<> struct CompileTimeAssert<true> {}; // helper macro to properly align structure members. // Calling INSERT_PADDING_BYTES will add a new member variable with a name like "pad121", // depending on the current source line to make sure variable names are unique. -#define INSERT_PADDING_BYTES_HELPER1(x, y) x ## y -#define INSERT_PADDING_BYTES_HELPER2(x, y) INSERT_PADDING_BYTES_HELPER1(x, y) -#define INSERT_PADDING_BYTES(num_words) u8 INSERT_PADDING_BYTES_HELPER2(pad, __LINE__)[(num_words)] +#define INSERT_PADDING_BYTES(num_bytes) u8 CONCAT2(pad, __LINE__)[(num_bytes)] +#define INSERT_PADDING_WORDS(num_words) u32 CONCAT2(pad, __LINE__)[(num_words)] #ifndef _MSC_VER #include <errno.h> -#ifdef __linux__ -#include <byteswap.h> -#elif defined __FreeBSD__ -#include <sys/endian.h> -#endif #if defined(__x86_64__) || defined(_M_X64) #define Crash() __asm__ __volatile__("int $3") @@ -148,98 +135,8 @@ inline u64 _rotr64(u64 x, unsigned int shift){ #define Crash() {DebugBreak();} #endif // _MSC_VER ndef -// Dolphin's min and max functions -#undef min -#undef max - -template<class T> -inline T min(const T& a, const T& b) {return a > b ? b : a;} -template<class T> -inline T max(const T& a, const T& b) {return a > b ? a : b;} - // Generic function to get last error message. // Call directly after the command or use the error num. // This function might change the error code. // Defined in Misc.cpp. const char* GetLastErrorMsg(); - -namespace Common -{ -inline u8 swap8(u8 _data) {return _data;} -inline u32 swap24(const u8* _data) {return (_data[0] << 16) | (_data[1] << 8) | _data[2];} - -#ifdef ANDROID -#undef swap16 -#undef swap32 -#undef swap64 -#endif - -#ifdef _MSC_VER -inline u16 swap16(u16 _data) {return _byteswap_ushort(_data);} -inline u32 swap32(u32 _data) {return _byteswap_ulong (_data);} -inline u64 swap64(u64 _data) {return _byteswap_uint64(_data);} -#elif _M_ARM -inline u16 swap16 (u16 _data) { u32 data = _data; __asm__ ("rev16 %0, %1\n" : "=l" (data) : "l" (data)); return (u16)data;} -inline u32 swap32 (u32 _data) {__asm__ ("rev %0, %1\n" : "=l" (_data) : "l" (_data)); return _data;} -inline u64 swap64(u64 _data) {return ((u64)swap32(_data) << 32) | swap32(_data >> 32);} -#elif __linux__ -inline u16 swap16(u16 _data) {return bswap_16(_data);} -inline u32 swap32(u32 _data) {return bswap_32(_data);} -inline u64 swap64(u64 _data) {return bswap_64(_data);} -#elif __APPLE__ -inline __attribute__((always_inline)) u16 swap16(u16 _data) - {return (_data >> 8) | (_data << 8);} -inline __attribute__((always_inline)) u32 swap32(u32 _data) - {return __builtin_bswap32(_data);} -inline __attribute__((always_inline)) u64 swap64(u64 _data) - {return __builtin_bswap64(_data);} -#elif __FreeBSD__ -inline u16 swap16(u16 _data) {return bswap16(_data);} -inline u32 swap32(u32 _data) {return bswap32(_data);} -inline u64 swap64(u64 _data) {return bswap64(_data);} -#else -// Slow generic implementation. -inline u16 swap16(u16 data) {return (data >> 8) | (data << 8);} -inline u32 swap32(u32 data) {return (swap16(data) << 16) | swap16(data >> 16);} -inline u64 swap64(u64 data) {return ((u64)swap32(data) << 32) | swap32(data >> 32);} -#endif - -inline u16 swap16(const u8* _pData) {return swap16(*(const u16*)_pData);} -inline u32 swap32(const u8* _pData) {return swap32(*(const u32*)_pData);} -inline u64 swap64(const u8* _pData) {return swap64(*(const u64*)_pData);} - -template <int count> -void swap(u8*); - -template <> -inline void swap<1>(u8* data) -{} - -template <> -inline void swap<2>(u8* data) -{ - *reinterpret_cast<u16*>(data) = swap16(data); -} - -template <> -inline void swap<4>(u8* data) -{ - *reinterpret_cast<u32*>(data) = swap32(data); -} - -template <> -inline void swap<8>(u8* data) -{ - *reinterpret_cast<u64*>(data) = swap64(data); -} - -template <typename T> -inline T FromBigEndian(T data) -{ - //static_assert(std::is_arithmetic<T>::value, "function only makes sense with arithmetic types"); - - swap<sizeof(data)>(reinterpret_cast<u8*>(&data)); - return data; -} - -} // Namespace Common diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 0ecf2d9de..eb43d589f 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -25,7 +25,7 @@ #ifdef USER_DIR #define EMU_DATA_DIR USER_DIR #else - #define EMU_DATA_DIR ".citra-emu" + #define EMU_DATA_DIR "citra-emu" #endif #endif diff --git a/src/common/emu_window.cpp b/src/common/emu_window.cpp index 48bb35db5..6459d2f32 100644 --- a/src/common/emu_window.cpp +++ b/src/common/emu_window.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "emu_window.h" +#include "video_core/video_core.h" void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) { Service::HID::PadState mapped_key = KeyMap::GetPadKey(key); @@ -15,3 +16,52 @@ void EmuWindow::KeyReleased(KeyMap::HostDeviceKey key) { Service::HID::PadButtonRelease(mapped_key); } + +EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, unsigned height) { + ASSERT(width > 0); + ASSERT(height > 0); + + EmuWindow::FramebufferLayout res = { width, height, {}, {} }; + + float window_aspect_ratio = static_cast<float>(height) / width; + float emulation_aspect_ratio = static_cast<float>(VideoCore::kScreenTopHeight * 2) / + VideoCore::kScreenTopWidth; + + if (window_aspect_ratio > emulation_aspect_ratio) { + // Window is narrower than the emulation content => apply borders to the top and bottom + int viewport_height = static_cast<int>(std::round(emulation_aspect_ratio * width)); + + res.top_screen.left = 0; + res.top_screen.right = res.top_screen.left + width; + res.top_screen.top = (height - viewport_height) / 2; + res.top_screen.bottom = res.top_screen.top + viewport_height / 2; + + int bottom_width = static_cast<int>((static_cast<float>(VideoCore::kScreenBottomWidth) / + VideoCore::kScreenTopWidth) * (res.top_screen.right - res.top_screen.left)); + int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2; + + res.bottom_screen.left = bottom_border; + res.bottom_screen.right = res.bottom_screen.left + bottom_width; + res.bottom_screen.top = res.top_screen.bottom; + res.bottom_screen.bottom = res.bottom_screen.top + viewport_height / 2; + } else { + // Otherwise, apply borders to the left and right sides of the window. + int viewport_width = static_cast<int>(std::round(height / emulation_aspect_ratio)); + + res.top_screen.left = (width - viewport_width) / 2; + res.top_screen.right = res.top_screen.left + viewport_width; + res.top_screen.top = 0; + res.top_screen.bottom = res.top_screen.top + height / 2; + + int bottom_width = static_cast<int>((static_cast<float>(VideoCore::kScreenBottomWidth) / + VideoCore::kScreenTopWidth) * (res.top_screen.right - res.top_screen.left)); + int bottom_border = ((res.top_screen.right - res.top_screen.left) - bottom_width) / 2; + + res.bottom_screen.left = res.top_screen.left + bottom_border; + res.bottom_screen.right = res.bottom_screen.left + bottom_width; + res.bottom_screen.top = res.top_screen.bottom; + res.bottom_screen.bottom = res.bottom_screen.top + height / 2; + } + + return res; +} diff --git a/src/common/emu_window.h b/src/common/emu_window.h index 1ad4b82a3..f6099fdb6 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -8,6 +8,7 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "common/key_map.h" +#include "common/math_util.h" /** * Abstraction class used to provide an interface between emulation code and the frontend @@ -38,6 +39,23 @@ public: std::pair<unsigned,unsigned> min_client_area_size; }; + /// Describes the layout of the window framebuffer (size and top/bottom screen positions) + struct FramebufferLayout { + + /** + * Factory method for constructing a default FramebufferLayout + * @param width Window framebuffer width in pixels + * @param height Window framebuffer height in pixels + * @return Newly created FramebufferLayout object with default screen regions initialized + */ + static FramebufferLayout DefaultScreenLayout(unsigned width, unsigned height); + + unsigned width; + unsigned height; + MathUtil::Rectangle<unsigned> top_screen; + MathUtil::Rectangle<unsigned> bottom_screen; + }; + /// Swap buffers to display the next frame virtual void SwapBuffers() = 0; @@ -75,11 +93,11 @@ public: } /** - * Gets the framebuffer size in pixels. + * Gets the framebuffer layout (width, height, and screen regions) * @note This method is thread-safe */ - const std::pair<unsigned,unsigned> GetFramebufferSize() const { - return framebuffer_size; + const FramebufferLayout& GetFramebufferLayout() const { + return framebuffer_layout; } /** @@ -118,11 +136,11 @@ protected: } /** - * Update internal framebuffer size with the given parameter. + * Update framebuffer layout with the given parameter. * @note EmuWindow implementations will usually use this in window resize event handlers. */ - void NotifyFramebufferSizeChanged(const std::pair<unsigned,unsigned>& size) { - framebuffer_size = size; + void NotifyFramebufferLayoutChanged(const FramebufferLayout& layout) { + framebuffer_layout = layout; } /** @@ -143,7 +161,7 @@ private: // By default, ignore this request and do nothing. } - std::pair<unsigned,unsigned> framebuffer_size; + FramebufferLayout framebuffer_layout; ///< Current framebuffer layout unsigned client_area_width; ///< Current client width, should be set by window impl. unsigned client_area_height; ///< Current client height, should be set by window impl. diff --git a/src/common/extended_trace.cpp b/src/common/extended_trace.cpp deleted file mode 100644 index cf7c346d4..000000000 --- a/src/common/extended_trace.cpp +++ /dev/null @@ -1,428 +0,0 @@ -// -------------------------------------------------------------------------------------- -// -// Written by Zoltan Csizmadia, zoltan_csizmadia@yahoo.com -// For companies(Austin,TX): If you would like to get my resume, send an email. -// -// The source is free, but if you want to use it, mention my name and e-mail address -// -// History: -// 1.0 Initial version Zoltan Csizmadia -// 1.1 WhineCube version Masken -// 1.2 Dolphin version Masken -// -// -------------------------------------------------------------------------------------- - -#if defined(WIN32) -#include <cstdio> -#include <windows.h> -#include "common/extended_trace.h" -#include "common/string_util.h" -using namespace std; - -#include <tchar.h> -#include <ImageHlp.h> - -#define BUFFERSIZE 0x200 -#pragma warning(disable:4996) - -// Unicode safe char* -> TCHAR* conversion -void PCSTR2LPTSTR( PCSTR lpszIn, LPTSTR lpszOut ) -{ -#if defined(UNICODE)||defined(_UNICODE) - ULONG index = 0; - PCSTR lpAct = lpszIn; - - for( ; ; lpAct++ ) - { - lpszOut[index++] = (TCHAR)(*lpAct); - if ( *lpAct == 0 ) - break; - } -#else - // This is trivial :) - strcpy( lpszOut, lpszIn ); -#endif -} - -// Let's figure out the path for the symbol files -// Search path= ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;" + lpszIniPath -// Note: There is no size check for lpszSymbolPath! -static void InitSymbolPath( PSTR lpszSymbolPath, PCSTR lpszIniPath ) -{ - CHAR lpszPath[BUFFERSIZE]; - - // Creating the default path - // ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;" - strcpy( lpszSymbolPath, "." ); - - // environment variable _NT_SYMBOL_PATH - if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", lpszPath, BUFFERSIZE ) ) - { - strcat( lpszSymbolPath, ";" ); - strcat( lpszSymbolPath, lpszPath ); - } - - // environment variable _NT_ALTERNATE_SYMBOL_PATH - if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", lpszPath, BUFFERSIZE ) ) - { - strcat( lpszSymbolPath, ";" ); - strcat( lpszSymbolPath, lpszPath ); - } - - // environment variable SYSTEMROOT - if ( GetEnvironmentVariableA( "SYSTEMROOT", lpszPath, BUFFERSIZE ) ) - { - strcat( lpszSymbolPath, ";" ); - strcat( lpszSymbolPath, lpszPath ); - strcat( lpszSymbolPath, ";" ); - - // SYSTEMROOT\System32 - strcat( lpszSymbolPath, lpszPath ); - strcat( lpszSymbolPath, "\\System32" ); - } - - // Add user defined path - if ( lpszIniPath != nullptr ) - if ( lpszIniPath[0] != '\0' ) - { - strcat( lpszSymbolPath, ";" ); - strcat( lpszSymbolPath, lpszIniPath ); - } -} - -// Uninitialize the loaded symbol files -BOOL UninitSymInfo() { - return SymCleanup( GetCurrentProcess() ); -} - -// Initializes the symbol files -BOOL InitSymInfo( PCSTR lpszInitialSymbolPath ) -{ - CHAR lpszSymbolPath[BUFFERSIZE]; - DWORD symOptions = SymGetOptions(); - - symOptions |= SYMOPT_LOAD_LINES; - symOptions &= ~SYMOPT_UNDNAME; - SymSetOptions( symOptions ); - InitSymbolPath( lpszSymbolPath, lpszInitialSymbolPath ); - - return SymInitialize( GetCurrentProcess(), lpszSymbolPath, TRUE); -} - -// Get the module name from a given address -static BOOL GetModuleNameFromAddress( UINT address, LPTSTR lpszModule ) -{ - BOOL ret = FALSE; - IMAGEHLP_MODULE moduleInfo; - - ::ZeroMemory( &moduleInfo, sizeof(moduleInfo) ); - moduleInfo.SizeOfStruct = sizeof(moduleInfo); - - if ( SymGetModuleInfo( GetCurrentProcess(), (DWORD)address, &moduleInfo ) ) - { - // Got it! - PCSTR2LPTSTR( moduleInfo.ModuleName, lpszModule ); - ret = TRUE; - } - else - // Not found :( - _tcscpy( lpszModule, _T("?") ); - - return ret; -} - -// Get function prototype and parameter info from ip address and stack address -static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, LPTSTR lpszSymbol ) -{ - BOOL ret = FALSE; - DWORD dwSymSize = 10000; - TCHAR lpszUnDSymbol[BUFFERSIZE]=_T("?"); - CHAR lpszNonUnicodeUnDSymbol[BUFFERSIZE]="?"; - LPTSTR lpszParamSep = nullptr; - LPTSTR lpszParsed = lpszUnDSymbol; - PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, dwSymSize ); - - ::ZeroMemory( pSym, dwSymSize ); - pSym->SizeOfStruct = dwSymSize; - pSym->MaxNameLength = dwSymSize - sizeof(IMAGEHLP_SYMBOL); - - // Set the default to unknown - _tcscpy( lpszSymbol, _T("?") ); - - // Get symbol info for IP -#ifndef _M_X64 - DWORD dwDisp = 0; - if ( SymGetSymFromAddr( GetCurrentProcess(), (ULONG)fnAddress, &dwDisp, pSym ) ) -#else - //makes it compile but hell im not sure if this works... - DWORD64 dwDisp = 0; - if ( SymGetSymFromAddr( GetCurrentProcess(), (ULONG)fnAddress, (PDWORD64)&dwDisp, pSym ) ) -#endif - { - // Make the symbol readable for humans - UnDecorateSymbolName( pSym->Name, lpszNonUnicodeUnDSymbol, BUFFERSIZE, - UNDNAME_COMPLETE | - UNDNAME_NO_THISTYPE | - UNDNAME_NO_SPECIAL_SYMS | - UNDNAME_NO_MEMBER_TYPE | - UNDNAME_NO_MS_KEYWORDS | - UNDNAME_NO_ACCESS_SPECIFIERS ); - - // Symbol information is ANSI string - PCSTR2LPTSTR( lpszNonUnicodeUnDSymbol, lpszUnDSymbol ); - - // I am just smarter than the symbol file :) - if (_tcscmp(lpszUnDSymbol, _T("_WinMain@16")) == 0) - _tcscpy(lpszUnDSymbol, _T("WinMain(HINSTANCE,HINSTANCE,LPCTSTR,int)")); - else if (_tcscmp(lpszUnDSymbol, _T("_main")) == 0) - _tcscpy(lpszUnDSymbol, _T("main(int,TCHAR * *)")); - else if (_tcscmp(lpszUnDSymbol, _T("_mainCRTStartup")) == 0) - _tcscpy(lpszUnDSymbol, _T("mainCRTStartup()")); - else if (_tcscmp(lpszUnDSymbol, _T("_wmain")) == 0) - _tcscpy(lpszUnDSymbol, _T("wmain(int,TCHAR * *,TCHAR * *)")); - else if (_tcscmp(lpszUnDSymbol, _T("_wmainCRTStartup")) == 0) - _tcscpy(lpszUnDSymbol, _T("wmainCRTStartup()")); - - lpszSymbol[0] = _T('\0'); - - // Let's go through the stack, and modify the function prototype, and insert the actual - // parameter values from the stack - if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == nullptr && _tcsstr( lpszUnDSymbol, _T("()") ) == nullptr) - { - ULONG index = 0; - for( ; ; index++ ) - { - lpszParamSep = _tcschr( lpszParsed, _T(',') ); - if ( lpszParamSep == nullptr ) - break; - - *lpszParamSep = _T('\0'); - - _tcscat( lpszSymbol, lpszParsed ); - _stprintf( lpszSymbol + _tcslen(lpszSymbol), _T("=0x%08X,"), *((ULONG*)(stackAddress) + 2 + index) ); - - lpszParsed = lpszParamSep + 1; - } - - lpszParamSep = _tcschr( lpszParsed, _T(')') ); - if ( lpszParamSep != nullptr ) - { - *lpszParamSep = _T('\0'); - - _tcscat( lpszSymbol, lpszParsed ); - _stprintf( lpszSymbol + _tcslen(lpszSymbol), _T("=0x%08X)"), *((ULONG*)(stackAddress) + 2 + index) ); - - lpszParsed = lpszParamSep + 1; - } - } - - _tcscat( lpszSymbol, lpszParsed ); - - ret = TRUE; - } - GlobalFree( pSym ); - - return ret; -} - -// Get source file name and line number from IP address -// The output format is: "sourcefile(linenumber)" or -// "modulename!address" or -// "address" -static BOOL GetSourceInfoFromAddress( UINT address, LPTSTR lpszSourceInfo ) -{ - BOOL ret = FALSE; - IMAGEHLP_LINE lineInfo; - DWORD dwDisp; - TCHAR lpszFileName[BUFFERSIZE] = _T(""); - TCHAR lpModuleInfo[BUFFERSIZE] = _T(""); - - _tcscpy( lpszSourceInfo, _T("?(?)") ); - - ::ZeroMemory( &lineInfo, sizeof( lineInfo ) ); - lineInfo.SizeOfStruct = sizeof( lineInfo ); - - if ( SymGetLineFromAddr( GetCurrentProcess(), address, &dwDisp, &lineInfo ) ) - { - // Got it. Let's use "sourcefile(linenumber)" format - PCSTR2LPTSTR( lineInfo.FileName, lpszFileName ); - TCHAR fname[_MAX_FNAME]; - TCHAR ext[_MAX_EXT]; - _tsplitpath(lpszFileName, nullptr, nullptr, fname, ext); - _stprintf( lpszSourceInfo, _T("%s%s(%d)"), fname, ext, lineInfo.LineNumber ); - ret = TRUE; - } - else - { - // There is no source file information. :( - // Let's use the "modulename!address" format - GetModuleNameFromAddress( address, lpModuleInfo ); - - if ( lpModuleInfo[0] == _T('?') || lpModuleInfo[0] == _T('\0')) - // There is no modulename information. :(( - // Let's use the "address" format - _stprintf( lpszSourceInfo, _T("0x%08X"), address ); - else - _stprintf( lpszSourceInfo, _T("%s!0x%08X"), lpModuleInfo, address ); - - ret = FALSE; - } - - return ret; -} - -void PrintFunctionAndSourceInfo(FILE* file, const STACKFRAME& callstack) -{ - TCHAR symInfo[BUFFERSIZE] = _T("?"); - TCHAR srcInfo[BUFFERSIZE] = _T("?"); - - GetFunctionInfoFromAddresses((ULONG)callstack.AddrPC.Offset, (ULONG)callstack.AddrFrame.Offset, symInfo); - GetSourceInfoFromAddress((ULONG)callstack.AddrPC.Offset, srcInfo); - etfprint(file, " " + Common::TStrToUTF8(srcInfo) + " : " + Common::TStrToUTF8(symInfo) + "\n"); -} - -void StackTrace( HANDLE hThread, const char* lpszMessage, FILE *file ) -{ - STACKFRAME callStack; - BOOL bResult; - CONTEXT context; - HANDLE hProcess = GetCurrentProcess(); - - // If it's not this thread, let's suspend it, and resume it at the end - if ( hThread != GetCurrentThread() ) - if ( SuspendThread( hThread ) == -1 ) - { - // whaaat ?! - etfprint(file, "Call stack info failed\n"); - return; - } - - ::ZeroMemory( &context, sizeof(context) ); - context.ContextFlags = CONTEXT_FULL; - - if ( !GetThreadContext( hThread, &context ) ) - { - etfprint(file, "Call stack info failed\n"); - return; - } - - ::ZeroMemory( &callStack, sizeof(callStack) ); -#ifndef _M_X64 - callStack.AddrPC.Offset = context.Eip; - callStack.AddrStack.Offset = context.Esp; - callStack.AddrFrame.Offset = context.Ebp; -#else - callStack.AddrPC.Offset = context.Rip; - callStack.AddrStack.Offset = context.Rsp; - callStack.AddrFrame.Offset = context.Rbp; -#endif - callStack.AddrPC.Mode = AddrModeFlat; - callStack.AddrStack.Mode = AddrModeFlat; - callStack.AddrFrame.Mode = AddrModeFlat; - - etfprint(file, "Call stack info: \n"); - etfprint(file, lpszMessage); - - PrintFunctionAndSourceInfo(file, callStack); - - for( ULONG index = 0; ; index++ ) - { - bResult = StackWalk( - IMAGE_FILE_MACHINE_I386, - hProcess, - hThread, - &callStack, - nullptr, - nullptr, - SymFunctionTableAccess, - SymGetModuleBase, - nullptr); - - if ( index == 0 ) - continue; - - if( !bResult || callStack.AddrFrame.Offset == 0 ) - break; - - PrintFunctionAndSourceInfo(file, callStack); - - } - - if ( hThread != GetCurrentThread() ) - ResumeThread( hThread ); -} - -void StackTrace(HANDLE hThread, const char* lpszMessage, FILE *file, DWORD eip, DWORD esp, DWORD ebp ) -{ - STACKFRAME callStack; - BOOL bResult; - TCHAR symInfo[BUFFERSIZE] = _T("?"); - TCHAR srcInfo[BUFFERSIZE] = _T("?"); - HANDLE hProcess = GetCurrentProcess(); - - // If it's not this thread, let's suspend it, and resume it at the end - if ( hThread != GetCurrentThread() ) - if ( SuspendThread( hThread ) == -1 ) - { - // whaaat ?! - etfprint(file, "Call stack info failed\n"); - return; - } - - ::ZeroMemory( &callStack, sizeof(callStack) ); - callStack.AddrPC.Offset = eip; - callStack.AddrStack.Offset = esp; - callStack.AddrFrame.Offset = ebp; - callStack.AddrPC.Mode = AddrModeFlat; - callStack.AddrStack.Mode = AddrModeFlat; - callStack.AddrFrame.Mode = AddrModeFlat; - - etfprint(file, "Call stack info: \n"); - etfprint(file, lpszMessage); - - PrintFunctionAndSourceInfo(file, callStack); - - for( ULONG index = 0; ; index++ ) - { - bResult = StackWalk( - IMAGE_FILE_MACHINE_I386, - hProcess, - hThread, - &callStack, - nullptr, - nullptr, - SymFunctionTableAccess, - SymGetModuleBase, - nullptr); - - if ( index == 0 ) - continue; - - if( !bResult || callStack.AddrFrame.Offset == 0 ) - break; - - PrintFunctionAndSourceInfo(file, callStack); - } - - if ( hThread != GetCurrentThread() ) - ResumeThread( hThread ); -} - -char g_uefbuf[2048]; - -void etfprintf(FILE *file, const char *format, ...) -{ - va_list ap; - va_start(ap, format); - int len = vsprintf(g_uefbuf, format, ap); - fwrite(g_uefbuf, 1, len, file); - va_end(ap); -} - -void etfprint(FILE *file, const std::string &text) -{ - size_t len = text.length(); - fwrite(text.data(), 1, len, file); -} - -#endif //WIN32 diff --git a/src/common/extended_trace.h b/src/common/extended_trace.h deleted file mode 100644 index ed3113a24..000000000 --- a/src/common/extended_trace.h +++ /dev/null @@ -1,50 +0,0 @@ -// ----------------------------------------------------------------------------------------- -// -// Written by Zoltan Csizmadia, zoltan_csizmadia@yahoo.com -// For companies(Austin,TX): If you would like to get my resume, send an email. -// -// The source is free, but if you want to use it, mention my name and e-mail address -// -// History: -// 1.0 Initial version Zoltan Csizmadia -// 1.1 WhineCube version Masken -// 1.2 Dolphin version Masken -// -// ---------------------------------------------------------------------------------------- - -#pragma once - -#if defined(WIN32) - -#include <windows.h> -#include <tchar.h> - -#include <string> - -#pragma comment( lib, "imagehlp.lib" ) - -#define EXTENDEDTRACEINITIALIZE( IniSymbolPath ) InitSymInfo( IniSymbolPath ) -#define EXTENDEDTRACEUNINITIALIZE() UninitSymInfo() -#define STACKTRACE(file) StackTrace( GetCurrentThread(), "", file) -#define STACKTRACE2(file, eip, esp, ebp) StackTrace(GetCurrentThread(), "", file, eip, esp, ebp) -// class File; - -BOOL InitSymInfo( PCSTR ); -BOOL UninitSymInfo(); -void StackTrace(HANDLE, char const* msg, FILE *file); -void StackTrace(HANDLE, char const* msg, FILE *file, DWORD eip, DWORD esp, DWORD ebp); - -// functions by Masken -void etfprintf(FILE *file, const char *format, ...); -void etfprint(FILE *file, const std::string &text); -#define UEFBUFSIZE 2048 -extern char g_uefbuf[UEFBUFSIZE]; - -#else // not WIN32 - -#define EXTENDEDTRACEINITIALIZE( IniSymbolPath ) ((void)0) -#define EXTENDEDTRACEUNINITIALIZE() ((void)0) -#define STACKTRACE(file) ((void)0) -#define STACKTRACE2(file, eip, esp, ebp) ((void)0) - -#endif // WIN32 diff --git a/src/common/file_search.cpp b/src/common/file_search.cpp deleted file mode 100644 index b3a0a84fb..000000000 --- a/src/common/file_search.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - - -#include "common/common.h" - -#ifndef _WIN32 -#include <dirent.h> -#else -#include <windows.h> -#endif - -#include <algorithm> - -#include "common/file_search.h" -#include "common/string_util.h" - - -CFileSearch::CFileSearch(const CFileSearch::XStringVector& _rSearchStrings, const CFileSearch::XStringVector& _rDirectories) -{ - // Reverse the loop order for speed? - for (size_t j = 0; j < _rSearchStrings.size(); j++) - { - for (size_t i = 0; i < _rDirectories.size(); i++) - { - FindFiles(_rSearchStrings[j], _rDirectories[i]); - } - } -} - - -void CFileSearch::FindFiles(const std::string& _searchString, const std::string& _strPath) -{ - std::string GCMSearchPath; - Common::BuildCompleteFilename(GCMSearchPath, _strPath, _searchString); -#ifdef _WIN32 - WIN32_FIND_DATA findData; - HANDLE FindFirst = FindFirstFile(Common::UTF8ToTStr(GCMSearchPath).c_str(), &findData); - - if (FindFirst != INVALID_HANDLE_VALUE) - { - bool bkeepLooping = true; - - while (bkeepLooping) - { - if (findData.cFileName[0] != '.') - { - std::string strFilename; - Common::BuildCompleteFilename(strFilename, _strPath, Common::TStrToUTF8(findData.cFileName)); - m_FileNames.push_back(strFilename); - } - - bkeepLooping = FindNextFile(FindFirst, &findData) ? true : false; - } - } - FindClose(FindFirst); - - -#else - // TODO: super lame/broken - - auto end_match(_searchString); - - // assuming we have a "*.blah"-like pattern - if (!end_match.empty() && end_match[0] == '*') - end_match.erase(0, 1); - - // ugly - if (end_match == ".*") - end_match.clear(); - - DIR* dir = opendir(_strPath.c_str()); - - if (!dir) - return; - - while (auto const dp = readdir(dir)) - { - std::string found(dp->d_name); - - if ((found != ".") && (found != "..") - && (found.size() >= end_match.size()) - && std::equal(end_match.rbegin(), end_match.rend(), found.rbegin())) - { - std::string full_name; - if (_strPath.c_str()[_strPath.size()-1] == DIR_SEP_CHR) - full_name = _strPath + found; - else - full_name = _strPath + DIR_SEP + found; - - m_FileNames.push_back(full_name); - } - } - - closedir(dir); -#endif -} - -const CFileSearch::XStringVector& CFileSearch::GetFileNames() const -{ - return m_FileNames; -} diff --git a/src/common/file_search.h b/src/common/file_search.h deleted file mode 100644 index 55ca02638..000000000 --- a/src/common/file_search.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <vector> - -class CFileSearch -{ -public: - typedef std::vector<std::string>XStringVector; - - CFileSearch(const XStringVector& _rSearchStrings, const XStringVector& _rDirectories); - const XStringVector& GetFileNames() const; - -private: - - void FindFiles(const std::string& _searchString, const std::string& _strPath); - - XStringVector m_FileNames; -}; diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 706e7c842..4ef4918d7 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -16,7 +16,10 @@ #include <tchar.h> #else #include <sys/param.h> +#include <sys/types.h> #include <dirent.h> +#include <pwd.h> +#include <unistd.h> #endif #if defined(__APPLE__) @@ -622,15 +625,64 @@ std::string GetBundleDirectory() #ifdef _WIN32 std::string& GetExeDirectory() { - static std::string DolphinPath; - if (DolphinPath.empty()) + static std::string exe_path; + if (exe_path.empty()) { - TCHAR Dolphin_exe_Path[2048]; - GetModuleFileName(nullptr, Dolphin_exe_Path, 2048); - DolphinPath = Common::TStrToUTF8(Dolphin_exe_Path); - DolphinPath = DolphinPath.substr(0, DolphinPath.find_last_of('\\')); + TCHAR tchar_exe_path[2048]; + GetModuleFileName(nullptr, tchar_exe_path, 2048); + exe_path = Common::TStrToUTF8(tchar_exe_path); + exe_path = exe_path.substr(0, exe_path.find_last_of('\\')); } - return DolphinPath; + return exe_path; +} +#else +/** + * @return The user’s home directory on POSIX systems + */ +static const std::string& GetHomeDirectory() { + static std::string home_path; + if (home_path.empty()) { + const char* envvar = getenv("HOME"); + if (envvar) { + home_path = envvar; + } else { + auto pw = getpwuid(getuid()); + ASSERT_MSG(pw, "$HOME isn’t defined, and the current user can’t be found in /etc/passwd."); + home_path = pw->pw_dir; + } + } + return home_path; +} + +/** + * Follows the XDG Base Directory Specification to get a directory path + * @param envvar The XDG environment variable to get the value from + * @return The directory path + * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ +static const std::string GetUserDirectory(const std::string& envvar) { + const char* directory = getenv(envvar.c_str()); + + std::string user_dir; + if (directory) { + user_dir = directory; + } else { + std::string subdirectory; + if (envvar == "XDG_DATA_HOME") + subdirectory = DIR_SEP ".local" DIR_SEP "share"; + else if (envvar == "XDG_CONFIG_HOME") + subdirectory = DIR_SEP ".config"; + else if (envvar == "XDG_CACHE_HOME") + subdirectory = DIR_SEP ".cache"; + else + ASSERT_MSG(false, "Unknown XDG variable %s.", envvar.c_str()); + user_dir = GetHomeDirectory() + subdirectory; + } + + ASSERT_MSG(!user_dir.empty(), "User directory %s musn’t be empty.", envvar.c_str()); + ASSERT_MSG(user_dir[0] == '/', "User directory %s must be absolute.", envvar.c_str()); + + return user_dir; } #endif @@ -661,20 +713,27 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new if (paths[D_USER_IDX].empty()) { #ifdef _WIN32 - paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; + paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; #else - if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) - paths[D_USER_IDX] = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; - else - paths[D_USER_IDX] = std::string(getenv("HOME") ? - getenv("HOME") : getenv("PWD") ? - getenv("PWD") : "") + DIR_SEP EMU_DATA_DIR DIR_SEP; + if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { + paths[D_USER_IDX] = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; + paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; + paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; + } else { + std::string data_dir = GetUserDirectory("XDG_DATA_HOME"); + std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME"); + std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME"); + + paths[D_USER_IDX] = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + paths[D_CONFIG_IDX] = config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + paths[D_CACHE_IDX] = cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + } #endif - paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; paths[D_GAMECONFIG_IDX] = paths[D_USER_IDX] + GAMECONFIG_DIR DIR_SEP; paths[D_MAPS_IDX] = paths[D_USER_IDX] + MAPS_DIR DIR_SEP; - paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; diff --git a/src/common/hash.cpp b/src/common/hash.cpp index fe2c9e636..0624dab8d 100644 --- a/src/common/hash.cpp +++ b/src/common/hash.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include "common/hash.h" #if _M_SSE >= 0x402 @@ -155,7 +156,7 @@ u64 GetMurmurHash3(const u8 *src, int len, u32 samples) const u8 * data = (const u8*)src; const int nblocks = len / 16; u32 Step = (len / 8); - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; @@ -233,7 +234,7 @@ u64 GetCRC32(const u8 *src, int len, u32 samples) u32 Step = (len / 8); const u64 *data = (const u64 *)src; const u64 *end = data + Step; - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; while(data < end) @@ -265,7 +266,7 @@ u64 GetHashHiresTexture(const u8 *src, int len, u32 samples) u32 Step = (len / 8); const u64 *data = (const u64 *)src; const u64 *end = data + Step; - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; while(data < end) @@ -308,7 +309,7 @@ u64 GetCRC32(const u8 *src, int len, u32 samples) u32 Step = (len/4); const u32 *data = (const u32 *)src; const u32 *end = data + Step; - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; while(data < end) @@ -380,7 +381,7 @@ u64 GetMurmurHash3(const u8* src, int len, u32 samples) u32 out[2]; const int nblocks = len / 8; u32 Step = (len / 4); - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; @@ -456,7 +457,7 @@ u64 GetHashHiresTexture(const u8 *src, int len, u32 samples) u32 Step = (len / 8); const u64 *data = (const u64 *)src; const u64 *end = data + Step; - if(samples == 0) samples = max(Step, 1u); + if(samples == 0) samples = std::max(Step, 1u); Step = Step / samples; if(Step < 1) Step = 1; while(data < end) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 8fee20a83..7b479b569 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -33,6 +33,7 @@ static std::shared_ptr<Logger> global_logger; CLS(Service) \ SUB(Service, SRV) \ SUB(Service, FS) \ + SUB(Service, ERR) \ SUB(Service, APT) \ SUB(Service, GSP) \ SUB(Service, AC) \ @@ -134,9 +135,18 @@ Entry CreateEntry(Class log_class, Level log_level, return std::move(entry); } +static Filter* filter; + +void SetFilter(Filter* new_filter) { + filter = new_filter; +} + void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, const char* format, ...) { + if (!filter->CheckMessage(log_class, log_level)) + return; + va_list args; va_start(args, format); Entry entry = CreateEntry(log_class, log_level, diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 1c44c929e..3114f864c 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -10,6 +10,7 @@ #include "common/concurrent_ring_buffer.h" +#include "common/logging/filter.h" #include "common/logging/log.h" namespace Log { @@ -131,4 +132,6 @@ Entry CreateEntry(Class log_class, Level log_level, /// Initializes the default Logger. std::shared_ptr<Logger> InitGlobalLogger(); +void SetFilter(Filter* filter); + } diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index c3da9989f..b53e4e633 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <array> #include <string> diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 6c5ca3968..7b67b3c07 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -53,6 +53,7 @@ enum class Class : ClassType { /// should have its own subclass. Service_SRV, ///< The SRV (Service Directory) implementation Service_FS, ///< The FS (Filesystem) service implementation + Service_ERR, ///< The ERR (Error) port implementation Service_APT, ///< The APT (Applets) service Service_GSP, ///< The GSP (GPU control) service Service_AC, ///< The AC (WiFi status) service diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index ef5739d84..36c91c4f6 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -11,7 +11,6 @@ #endif #include "common/logging/backend.h" -#include "common/logging/filter.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" @@ -116,7 +115,7 @@ void PrintColoredMessage(const Entry& entry) { #endif } -void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) { +void TextLoggingLoop(std::shared_ptr<Logger> logger) { std::array<Entry, 256> entry_buffer; while (true) { @@ -126,9 +125,7 @@ void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) { } for (size_t i = 0; i < num_entries; ++i) { const Entry& entry = entry_buffer[i]; - if (filter->CheckMessage(entry.log_class, entry.log_level)) { - PrintColoredMessage(entry); - } + PrintColoredMessage(entry); } } } diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 2f05794f0..8474a1904 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -11,7 +11,6 @@ namespace Log { class Logger; struct Entry; -class Filter; /** * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's @@ -36,6 +35,6 @@ void PrintColoredMessage(const Entry& entry); * Logging loop that repeatedly reads messages from the provided logger and prints them to the * console. It is the baseline barebones log outputter. */ -void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter); +void TextLoggingLoop(std::shared_ptr<Logger> logger); } diff --git a/src/common/mem_arena.cpp b/src/common/mem_arena.cpp index a20361d6f..76c70701d 100644 --- a/src/common/mem_arena.cpp +++ b/src/common/mem_arena.cpp @@ -116,7 +116,7 @@ void MemArena::GrabLowMemSpace(size_t size) GetSystemInfo(&sysInfo); #elif defined(ANDROID) // Use ashmem so we don't have to allocate a file on disk! - fd = ashmem_create_region("PPSSPP_RAM", size); + fd = ashmem_create_region("Citra_RAM", size); // Note that it appears that ashmem is pinned by default, so no need to pin. if (fd < 0) { @@ -218,7 +218,7 @@ u8* MemArena::Find4GBBase() void* base = mmap(0, 0x10000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); if (base == MAP_FAILED) { - PanicAlert("Failed to map 256 MB of memory space: %s", strerror(errno)); + LOG_ERROR(Common_Memory, "Failed to map 256 MB of memory space: %s", strerror(errno)); return 0; } munmap(base, 0x10000000); @@ -338,7 +338,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena // address space. if (!Memory_TryBase(base, views, num_views, flags, arena)) { - PanicAlert("MemoryMap_Setup: Failed finding a memory base."); + LOG_ERROR(Common_Memory, "MemoryMap_Setup: Failed finding a memory base."); return 0; } #elif defined(_WIN32) @@ -363,12 +363,11 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena if (!Memory_TryBase(base, views, num_views, flags, arena)) { LOG_ERROR(Common_Memory, "MemoryMap_Setup: Failed finding a memory base."); - PanicAlert("MemoryMap_Setup: Failed finding a memory base."); return 0; } #endif if (base_attempts) - PanicAlert("No possible memory base pointer found!"); + LOG_ERROR(Common_Memory, "No possible memory base pointer found!"); return base; } diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index 8f982da89..7e69d31cb 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -56,7 +56,7 @@ void* AllocateExecutableMemory(size_t size, bool low) { ptr = nullptr; #endif - PanicAlert("Failed to allocate executable memory"); + LOG_ERROR(Common_Memory, "Failed to allocate executable memory"); } #if !defined(_WIN32) && defined(__x86_64__) && !defined(MAP_32BIT) else @@ -72,7 +72,7 @@ void* AllocateExecutableMemory(size_t size, bool low) #if defined(_M_X64) if ((u64)ptr >= 0x80000000 && low == true) - PanicAlert("Executable memory ended up above 2GB!"); + LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!"); #endif return ptr; @@ -94,7 +94,7 @@ void* AllocateMemoryPages(size_t size) // (unsigned long)size); if (ptr == nullptr) - PanicAlert("Failed to allocate raw memory"); + LOG_ERROR(Common_Memory, "Failed to allocate raw memory"); return ptr; } @@ -117,7 +117,7 @@ void* AllocateAlignedMemory(size_t size,size_t alignment) // (unsigned long)size); if (ptr == nullptr) - PanicAlert("Failed to allocate aligned memory"); + LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); return ptr; } @@ -129,7 +129,7 @@ void FreeMemoryPages(void* ptr, size_t size) #ifdef _WIN32 if (!VirtualFree(ptr, 0, MEM_RELEASE)) - PanicAlert("FreeMemoryPages failed!\n%s", GetLastErrorMsg()); + LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n%s", GetLastErrorMsg()); ptr = nullptr; // Is this our responsibility? #else @@ -155,7 +155,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) - PanicAlert("WriteProtectMemory failed!\n%s", GetLastErrorMsg()); + LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n%s", GetLastErrorMsg()); #else mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ); #endif @@ -166,7 +166,7 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &oldValue)) - PanicAlert("UnWriteProtectMemory failed!\n%s", GetLastErrorMsg()); + LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n%s", GetLastErrorMsg()); #else mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ); #endif diff --git a/src/common/msg_handler.cpp b/src/common/msg_handler.cpp deleted file mode 100644 index 4a47b518e..000000000 --- a/src/common/msg_handler.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstdio> - -#include "common/common.h" // Local -#include "common/string_util.h" - -bool DefaultMsgHandler(const char* caption, const char* text, bool yes_no, int Style); -static MsgAlertHandler msg_handler = DefaultMsgHandler; -static bool AlertEnabled = true; - -std::string DefaultStringTranslator(const char* text); -static StringTranslator str_translator = DefaultStringTranslator; - -// Select which of these functions that are used for message boxes. If -// wxWidgets is enabled we will use wxMsgAlert() that is defined in Main.cpp -void RegisterMsgAlertHandler(MsgAlertHandler handler) -{ - msg_handler = handler; -} - -// Select translation function. For wxWidgets use wxStringTranslator in Main.cpp -void RegisterStringTranslator(StringTranslator translator) -{ - str_translator = translator; -} - -// enable/disable the alert handler -void SetEnableAlert(bool enable) -{ - AlertEnabled = enable; -} - -// This is the first stop for gui alerts where the log is updated and the -// correct window is shown -bool MsgAlert(bool yes_no, int Style, const char* format, ...) -{ - // Read message and write it to the log - std::string caption; - char buffer[2048]; - - static std::string info_caption; - static std::string warn_caption; - static std::string ques_caption; - static std::string crit_caption; - - if (!info_caption.length()) - { - info_caption = str_translator(_trans("Information")); - ques_caption = str_translator(_trans("Question")); - warn_caption = str_translator(_trans("Warning")); - crit_caption = str_translator(_trans("Critical")); - } - - switch(Style) - { - case INFORMATION: - caption = info_caption; - break; - case QUESTION: - caption = ques_caption; - break; - case WARNING: - caption = warn_caption; - break; - case CRITICAL: - caption = crit_caption; - break; - } - - va_list args; - va_start(args, format); - Common::CharArrayFromFormatV(buffer, sizeof(buffer)-1, str_translator(format).c_str(), args); - va_end(args); - - LOG_INFO(Common, "%s: %s", caption.c_str(), buffer); - - // Don't ignore questions, especially AskYesNo, PanicYesNo could be ignored - if (msg_handler && (AlertEnabled || Style == QUESTION || Style == CRITICAL)) - return msg_handler(caption.c_str(), buffer, yes_no, Style); - - return true; -} - -// Default non library dependent panic alert -bool DefaultMsgHandler(const char* caption, const char* text, bool yes_no, int Style) -{ -//#ifdef _WIN32 -// int STYLE = MB_ICONINFORMATION; -// if (Style == QUESTION) STYLE = MB_ICONQUESTION; -// if (Style == WARNING) STYLE = MB_ICONWARNING; -// -// return IDYES == MessageBox(0, UTF8ToTStr(text).c_str(), UTF8ToTStr(caption).c_str(), STYLE | (yes_no ? MB_YESNO : MB_OK)); -//#else - printf("%s\n", text); - return true; -//#endif -} - -// Default (non) translator -std::string DefaultStringTranslator(const char* text) -{ - return text; -} - diff --git a/src/common/msg_handler.h b/src/common/msg_handler.h deleted file mode 100644 index 421f93e23..000000000 --- a/src/common/msg_handler.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> - -// Message alerts -enum MSG_TYPE -{ - INFORMATION, - QUESTION, - WARNING, - CRITICAL -}; - -typedef bool (*MsgAlertHandler)(const char* caption, const char* text, - bool yes_no, int Style); -typedef std::string (*StringTranslator)(const char* text); - -void RegisterMsgAlertHandler(MsgAlertHandler handler); -void RegisterStringTranslator(StringTranslator translator); - -extern bool MsgAlert(bool yes_no, int Style, const char* format, ...) -#ifdef __GNUC__ - __attribute__((format(printf, 3, 4))) -#endif - ; -void SetEnableAlert(bool enable); - -#ifdef _MSC_VER - #define SuccessAlert(format, ...) MsgAlert(false, INFORMATION, format, __VA_ARGS__) - #define PanicAlert(format, ...) MsgAlert(false, WARNING, format, __VA_ARGS__) - #define PanicYesNo(format, ...) MsgAlert(true, WARNING, format, __VA_ARGS__) - #define AskYesNo(format, ...) MsgAlert(true, QUESTION, format, __VA_ARGS__) - #define CriticalAlert(format, ...) MsgAlert(false, CRITICAL, format, __VA_ARGS__) - // Use these macros (that do the same thing) if the message should be translated. - #define SuccessAlertT(format, ...) MsgAlert(false, INFORMATION, format, __VA_ARGS__) - #define PanicAlertT(format, ...) MsgAlert(false, WARNING, format, __VA_ARGS__) - #define PanicYesNoT(format, ...) MsgAlert(true, WARNING, format, __VA_ARGS__) - #define AskYesNoT(format, ...) MsgAlert(true, QUESTION, format, __VA_ARGS__) - #define CriticalAlertT(format, ...) MsgAlert(false, CRITICAL, format, __VA_ARGS__) -#else - #define SuccessAlert(format, ...) MsgAlert(false, INFORMATION, format, ##__VA_ARGS__) - #define PanicAlert(format, ...) MsgAlert(false, WARNING, format, ##__VA_ARGS__) - #define PanicYesNo(format, ...) MsgAlert(true, WARNING, format, ##__VA_ARGS__) - #define AskYesNo(format, ...) MsgAlert(true, QUESTION, format, ##__VA_ARGS__) - #define CriticalAlert(format, ...) MsgAlert(false, CRITICAL, format, ##__VA_ARGS__) - // Use these macros (that do the same thing) if the message should be translated. - #define SuccessAlertT(format, ...) MsgAlert(false, INFORMATION, format, ##__VA_ARGS__) - #define PanicAlertT(format, ...) MsgAlert(false, WARNING, format, ##__VA_ARGS__) - #define PanicYesNoT(format, ...) MsgAlert(true, WARNING, format, ##__VA_ARGS__) - #define AskYesNoT(format, ...) MsgAlert(true, QUESTION, format, ##__VA_ARGS__) - #define CriticalAlertT(format, ...) MsgAlert(false, CRITICAL, format, ##__VA_ARGS__) -#endif diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp new file mode 100644 index 000000000..65c3df167 --- /dev/null +++ b/src/common/profiler.cpp @@ -0,0 +1,182 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/profiler.h" +#include "common/profiler_reporting.h" +#include "common/assert.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013. +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> // For QueryPerformanceCounter/Frequency +#endif + +namespace Common { +namespace Profiling { + +#if ENABLE_PROFILING +thread_local Timer* Timer::current_timer = nullptr; +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013 +QPCClock::time_point QPCClock::now() { + static LARGE_INTEGER freq; + // Use this dummy local static to ensure this gets initialized once. + static BOOL dummy = QueryPerformanceFrequency(&freq); + + LARGE_INTEGER ticks; + QueryPerformanceCounter(&ticks); + + // This is prone to overflow when multiplying, which is why I'm using micro instead of nano. The + // correct way to approach this would be to just return ticks as a time_point and then subtract + // and do this conversion when creating a duration from two time_points, however, as far as I + // could tell the C++ requirements for these types are incompatible with this approach. + return time_point(duration(ticks.QuadPart * std::micro::den / freq.QuadPart)); +} +#endif + +TimingCategory::TimingCategory(const char* name, TimingCategory* parent) + : accumulated_duration(0) { + + ProfilingManager& manager = GetProfilingManager(); + category_id = manager.RegisterTimingCategory(this, name); + if (parent != nullptr) + manager.SetTimingCategoryParent(category_id, parent->category_id); +} + +ProfilingManager::ProfilingManager() + : last_frame_end(Clock::now()), this_frame_start(Clock::now()) { +} + +unsigned int ProfilingManager::RegisterTimingCategory(TimingCategory* category, const char* name) { + TimingCategoryInfo info; + info.category = category; + info.name = name; + info.parent = TimingCategoryInfo::NO_PARENT; + + unsigned int id = (unsigned int)timing_categories.size(); + timing_categories.push_back(std::move(info)); + + return id; +} + +void ProfilingManager::SetTimingCategoryParent(unsigned int category, unsigned int parent) { + ASSERT(category < timing_categories.size()); + ASSERT(parent < timing_categories.size()); + + timing_categories[category].parent = parent; +} + +void ProfilingManager::BeginFrame() { + this_frame_start = Clock::now(); +} + +void ProfilingManager::FinishFrame() { + Clock::time_point now = Clock::now(); + + results.interframe_time = now - last_frame_end; + results.frame_time = now - this_frame_start; + + results.time_per_category.resize(timing_categories.size()); + for (size_t i = 0; i < timing_categories.size(); ++i) { + results.time_per_category[i] = timing_categories[i].category->GetAccumulatedTime(); + } + + last_frame_end = now; +} + +TimingResultsAggregator::TimingResultsAggregator(size_t window_size) + : max_window_size(window_size), window_size(0) { + interframe_times.resize(window_size, Duration::zero()); + frame_times.resize(window_size, Duration::zero()); +} + +void TimingResultsAggregator::Clear() { + window_size = cursor = 0; +} + +void TimingResultsAggregator::SetNumberOfCategories(size_t n) { + size_t old_size = times_per_category.size(); + if (n == old_size) + return; + + times_per_category.resize(n); + + for (size_t i = old_size; i < n; ++i) { + times_per_category[i].resize(max_window_size, Duration::zero()); + } +} + +void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { + SetNumberOfCategories(frame_result.time_per_category.size()); + + interframe_times[cursor] = frame_result.interframe_time; + frame_times[cursor] = frame_result.frame_time; + for (size_t i = 0; i < frame_result.time_per_category.size(); ++i) { + times_per_category[i][cursor] = frame_result.time_per_category[i]; + } + + ++cursor; + if (cursor == max_window_size) + cursor = 0; + if (window_size < max_window_size) + ++window_size; +} + +static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) { + AggregatedDuration result; + result.avg = Duration::zero(); + + result.min = result.max = (len == 0 ? Duration::zero() : v[0]); + + for (size_t i = 1; i < len; ++i) { + Duration value = v[i]; + result.avg += value; + result.min = std::min(result.min, value); + result.max = std::max(result.max, value); + } + if (len != 0) + result.avg /= len; + + return result; +} + +static float tof(Common::Profiling::Duration dur) { + using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; + return std::chrono::duration_cast<FloatMs>(dur).count(); +} + +AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { + AggregatedFrameResult result; + + result.interframe_time = AggregateField(interframe_times, window_size); + result.frame_time = AggregateField(frame_times, window_size); + + if (result.interframe_time.avg != Duration::zero()) { + result.fps = 1000.0f / tof(result.interframe_time.avg); + } else { + result.fps = 0.0f; + } + + result.time_per_category.resize(times_per_category.size()); + for (size_t i = 0; i < times_per_category.size(); ++i) { + result.time_per_category[i] = AggregateField(times_per_category[i], window_size); + } + + return result; +} + +ProfilingManager& GetProfilingManager() { + // Takes advantage of "magic" static initialization for race-free initialization. + static ProfilingManager manager; + return manager; +} + +SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() { + static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); + return SynchronizedRef<TimingResultsAggregator>(aggregator); +} + +} // namespace Profiling +} // namespace Common diff --git a/src/common/profiler.h b/src/common/profiler.h new file mode 100644 index 000000000..3e967b4bc --- /dev/null +++ b/src/common/profiler.h @@ -0,0 +1,152 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <chrono> + +#include "common/assert.h" +#include "common/thread.h" + +namespace Common { +namespace Profiling { + +// If this is defined to 0, it turns all Timers into no-ops. +#ifndef ENABLE_PROFILING +#define ENABLE_PROFILING 1 +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013 +// MSVC up to 2013 doesn't use QueryPerformanceCounter for high_resolution_clock, so it has bad +// precision. We manually implement a clock based on QPC to get good results. + +struct QPCClock { + using duration = std::chrono::microseconds; + using time_point = std::chrono::time_point<QPCClock>; + using rep = duration::rep; + using period = duration::period; + static const bool is_steady = false; + + static time_point now(); +}; + +using Clock = QPCClock; +#else +using Clock = std::chrono::high_resolution_clock; +#endif + +using Duration = Clock::duration; + +/** + * Represents a timing category that measured time can be accounted towards. Should be declared as a + * global variable and passed to Timers. + */ +class TimingCategory final { +public: + TimingCategory(const char* name, TimingCategory* parent = nullptr); + + unsigned int GetCategoryId() const { + return category_id; + } + + /// Adds some time to this category. Can safely be called from multiple threads at the same time. + void AddTime(Duration amount) { + std::atomic_fetch_add_explicit( + &accumulated_duration, amount.count(), + std::memory_order_relaxed); + } + + /** + * Atomically retrieves the accumulated measured time for this category and resets the counter + * to zero. Can be safely called concurrently with AddTime. + */ + Duration GetAccumulatedTime() { + return Duration(std::atomic_exchange_explicit( + &accumulated_duration, (Duration::rep)0, + std::memory_order_relaxed)); + } + +private: + unsigned int category_id; + std::atomic<Duration::rep> accumulated_duration; +}; + +/** + * Measures time elapsed between a call to Start and a call to Stop and attributes it to the given + * TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be + * appropriately paired. + * + * When a Timer is started, it automatically pauses a previously running timer on the same thread, + * which is resumed when it is stopped. As such, no special action needs to be taken to avoid + * double-accounting of time on two categories. + */ +class Timer { +public: + Timer(TimingCategory& category) : category(category) { + } + + void Start() { +#if ENABLE_PROFILING + ASSERT(!running); + previous_timer = current_timer; + current_timer = this; + if (previous_timer != nullptr) + previous_timer->StopTiming(); + + StartTiming(); +#endif + } + + void Stop() { +#if ENABLE_PROFILING + ASSERT(running); + StopTiming(); + + if (previous_timer != nullptr) + previous_timer->StartTiming(); + current_timer = previous_timer; +#endif + } + +private: +#if ENABLE_PROFILING + void StartTiming() { + start = Clock::now(); + running = true; + } + + void StopTiming() { + auto duration = Clock::now() - start; + running = false; + category.AddTime(std::chrono::duration_cast<Duration>(duration)); + } + + Clock::time_point start; + bool running = false; + + Timer* previous_timer; + static thread_local Timer* current_timer; +#endif + + TimingCategory& category; +}; + +/** + * A Timer that automatically starts timing when created and stops at the end of the scope. Should + * be used in the majority of cases. + */ +class ScopeTimer : public Timer { +public: + ScopeTimer(TimingCategory& category) : Timer(category) { + Start(); + } + + ~ScopeTimer() { + Stop(); + } +}; + +} // namespace Profiling +} // namespace Common diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h new file mode 100644 index 000000000..3abb73315 --- /dev/null +++ b/src/common/profiler_reporting.h @@ -0,0 +1,108 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <chrono> +#include <mutex> +#include <utility> +#include <vector> + +#include "common/profiler.h" +#include "common/synchronized_wrapper.h" + +namespace Common { +namespace Profiling { + +struct TimingCategoryInfo { + static const unsigned int NO_PARENT = -1; + + TimingCategory* category; + const char* name; + unsigned int parent; +}; + +struct ProfilingFrameResult { + /// Time since the last delivered frame + Duration interframe_time; + + /// Time spent processing a frame, excluding VSync + Duration frame_time; + + /// Total amount of time spent inside each category in this frame. Indexed by the category id + std::vector<Duration> time_per_category; +}; + +class ProfilingManager final { +public: + ProfilingManager(); + + unsigned int RegisterTimingCategory(TimingCategory* category, const char* name); + void SetTimingCategoryParent(unsigned int category, unsigned int parent); + + const std::vector<TimingCategoryInfo>& GetTimingCategoriesInfo() const { + return timing_categories; + } + + /// This should be called after swapping screen buffers. + void BeginFrame(); + /// This should be called before swapping screen buffers. + void FinishFrame(); + + /// Get the timing results from the previous frame. This is updated when you call FinishFrame(). + const ProfilingFrameResult& GetPreviousFrameResults() const { + return results; + } + +private: + std::vector<TimingCategoryInfo> timing_categories; + Clock::time_point last_frame_end; + Clock::time_point this_frame_start; + + ProfilingFrameResult results; +}; + +struct AggregatedDuration { + Duration avg, min, max; +}; + +struct AggregatedFrameResult { + /// Time since the last delivered frame + AggregatedDuration interframe_time; + + /// Time spent processing a frame, excluding VSync + AggregatedDuration frame_time; + + float fps; + + /// Total amount of time spent inside each category in this frame. Indexed by the category id + std::vector<AggregatedDuration> time_per_category; +}; + +class TimingResultsAggregator final { +public: + TimingResultsAggregator(size_t window_size); + + void Clear(); + void SetNumberOfCategories(size_t n); + + void AddFrame(const ProfilingFrameResult& frame_result); + + AggregatedFrameResult GetAggregatedResults() const; + + size_t max_window_size; + size_t window_size; + size_t cursor; + + std::vector<Duration> interframe_times; + std::vector<Duration> frame_times; + std::vector<std::vector<Duration>> times_per_category; +}; + +ProfilingManager& GetProfilingManager(); +SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); + +} // namespace Profiling +} // namespace Common diff --git a/src/common/swap.h b/src/common/swap.h index e2d918362..7e37655bf 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -17,18 +17,14 @@ #pragma once -// Android -#if defined(ANDROID) +#if defined(__linux__) +#include <byteswap.h> +#elif defined(__FreeBSD__) #include <sys/endian.h> - -#if _BYTE_ORDER == _LITTLE_ENDIAN && !defined(COMMON_LITTLE_ENDIAN) -#define COMMON_LITTLE_ENDIAN 1 -#elif _BYTE_ORDER == _BIG_ENDIAN && !defined(COMMON_BIG_ENDIAN) -#define COMMON_BIG_ENDIAN 1 #endif // GCC 4.6+ -#elif __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) #if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN) #define COMMON_LITTLE_ENDIAN 1 @@ -49,7 +45,6 @@ #elif defined(_MSC_VER) && !defined(COMMON_BIG_ENDIAN) && !defined(COMMON_LITTLE_ENDIAN) #define COMMON_LITTLE_ENDIAN 1 - #endif // Worst case, default to little endian. @@ -57,6 +52,93 @@ #define COMMON_LITTLE_ENDIAN 1 #endif +namespace Common { + +inline u8 swap8(u8 _data) {return _data;} +inline u32 swap24(const u8* _data) {return (_data[0] << 16) | (_data[1] << 8) | _data[2];} + +#ifdef _MSC_VER +inline u16 swap16(u16 _data) {return _byteswap_ushort(_data);} +inline u32 swap32(u32 _data) {return _byteswap_ulong (_data);} +inline u64 swap64(u64 _data) {return _byteswap_uint64(_data);} +#elif _M_ARM +inline u16 swap16 (u16 _data) { u32 data = _data; __asm__ ("rev16 %0, %1\n" : "=l" (data) : "l" (data)); return (u16)data;} +inline u32 swap32 (u32 _data) {__asm__ ("rev %0, %1\n" : "=l" (_data) : "l" (_data)); return _data;} +inline u64 swap64(u64 _data) {return ((u64)swap32(_data) << 32) | swap32(_data >> 32);} +#elif __linux__ +inline u16 swap16(u16 _data) {return bswap_16(_data);} +inline u32 swap32(u32 _data) {return bswap_32(_data);} +inline u64 swap64(u64 _data) {return bswap_64(_data);} +#elif __APPLE__ +inline __attribute__((always_inline)) u16 swap16(u16 _data) +{return (_data >> 8) | (_data << 8);} +inline __attribute__((always_inline)) u32 swap32(u32 _data) +{return __builtin_bswap32(_data);} +inline __attribute__((always_inline)) u64 swap64(u64 _data) +{return __builtin_bswap64(_data);} +#elif __FreeBSD__ +inline u16 swap16(u16 _data) {return bswap16(_data);} +inline u32 swap32(u32 _data) {return bswap32(_data);} +inline u64 swap64(u64 _data) {return bswap64(_data);} +#else +// Slow generic implementation. +inline u16 swap16(u16 data) {return (data >> 8) | (data << 8);} +inline u32 swap32(u32 data) {return (swap16(data) << 16) | swap16(data >> 16);} +inline u64 swap64(u64 data) {return ((u64)swap32(data) << 32) | swap32(data >> 32);} +#endif + +inline float swapf(float f) { + union { + float f; + unsigned int u32; + } dat1, dat2; + + dat1.f = f; + dat2.u32 = swap32(dat1.u32); + + return dat2.f; +} + +inline double swapd(double f) { + union { + double f; + unsigned long long u64; + } dat1, dat2; + + dat1.f = f; + dat2.u64 = swap64(dat1.u64); + + return dat2.f; +} + +inline u16 swap16(const u8* _pData) {return swap16(*(const u16*)_pData);} +inline u32 swap32(const u8* _pData) {return swap32(*(const u32*)_pData);} +inline u64 swap64(const u8* _pData) {return swap64(*(const u64*)_pData);} + +template <int count> +void swap(u8*); + +template <> +inline void swap<1>(u8* data) { } + +template <> +inline void swap<2>(u8* data) { + *reinterpret_cast<u16*>(data) = swap16(data); +} + +template <> +inline void swap<4>(u8* data) { + *reinterpret_cast<u32*>(data) = swap32(data); +} + +template <> +inline void swap<8>(u8* data) { + *reinterpret_cast<u64*>(data) = swap64(data); +} + +} // Namespace Common + + template <typename T, typename F> struct swap_struct_t { typedef swap_struct_t<T, F> swapped_t; @@ -448,35 +530,35 @@ bool operator==(const S &p, const swap_struct_t<T, F> v) { template <typename T> struct swap_64_t { static T swap(T x) { - return (T)bswap64(*(u64 *)&x); + return (T)Common::swap64(*(u64 *)&x); } }; template <typename T> struct swap_32_t { static T swap(T x) { - return (T)bswap32(*(u32 *)&x); + return (T)Common::swap32(*(u32 *)&x); } }; template <typename T> struct swap_16_t { static T swap(T x) { - return (T)bswap16(*(u16 *)&x); + return (T)Common::swap16(*(u16 *)&x); } }; template <typename T> struct swap_float_t { static T swap(T x) { - return (T)bswapf(*(float *)&x); + return (T)Common::swapf(*(float *)&x); } }; template <typename T> struct swap_double_t { static T swap(T x) { - return (T)bswapd(*(double *)&x); + return (T)Common::swapd(*(double *)&x); } }; @@ -527,4 +609,5 @@ typedef s64 s64_be; typedef float float_be; typedef double double_be; + #endif diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h new file mode 100644 index 000000000..946252b8c --- /dev/null +++ b/src/common/synchronized_wrapper.h @@ -0,0 +1,69 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <mutex> + +namespace Common { + +/** + * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no + * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a + * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type (http://doc.rust-lang.org/std/sync/struct.Mutex.html). + */ +template <typename T> +class SynchronizedWrapper { +public: + template <typename... Args> + SynchronizedWrapper(Args&&... args) : + data(std::forward<Args>(args)...) { + } + +private: + template <typename U> + friend class SynchronizedRef; + + std::mutex mutex; + T data; +}; + +/** + * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This + * greatly reduces the chance that someone will access the wrapped resource without locking the + * mutex. + */ +template <typename T> +class SynchronizedRef { +public: + SynchronizedRef(SynchronizedWrapper<T>& wrapper) : wrapper(&wrapper) { + wrapper.mutex.lock(); + } + + SynchronizedRef(SynchronizedRef&) = delete; + SynchronizedRef(SynchronizedRef&& o) : wrapper(o.wrapper) { + o.wrapper = nullptr; + } + + ~SynchronizedRef() { + if (wrapper) + wrapper->mutex.unlock(); + } + + SynchronizedRef& operator=(SynchronizedRef&) = delete; + SynchronizedRef& operator=(SynchronizedRef&& o) { + std::swap(wrapper, o.wrapper); + } + + T& operator*() { return wrapper->data; } + const T& operator*() const { return wrapper->data; } + + T* operator->() { return &wrapper->data; } + const T* operator->() const { return &wrapper->data; } + +private: + SynchronizedWrapper<T>* wrapper; +}; + +} // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index eaf1ba00c..a45728e1e 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -24,6 +24,25 @@ #include <unistd.h> #endif +// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very +// recently. Fortunately, thread local variables have been well supported for compilers for a while, +// but with semantics supporting only POD types, so we can use a few defines to get some amount of +// backwards compat support. +// WARNING: This only works correctly with POD types. +#if defined(__clang__) +# if !__has_feature(cxx_thread_local) +# define thread_local __thread +# endif +#elif defined(__GNUC__) +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) +# define thread_local __thread +# endif +#elif defined(_MSC_VER) +# if _MSC_VER < 1900 +# define thread_local __declspec(thread) +# endif +#endif + namespace Common { diff --git a/src/common/utf8.cpp b/src/common/utf8.cpp deleted file mode 100644 index 56609634c..000000000 --- a/src/common/utf8.cpp +++ /dev/null @@ -1,459 +0,0 @@ -/* - Basic UTF-8 manipulation routines - by Jeff Bezanson - placed in the public domain Fall 2005 - - This code is designed to provide the utilities you need to manipulate - UTF-8 as an internal string encoding. These functions do not perform the - error checking normally needed when handling UTF-8 data, so if you happen - to be from the Unicode Consortium you will want to flay me alive. - I do this because error checking can be performed at the boundaries (I/O), - with these routines reserved for higher performance on data known to be - valid. -*/ - -#ifdef _WIN32 -#include <windows.h> -#undef min -#undef max -#endif - -#include <cstdlib> -#include <cstring> -#include <algorithm> - -#include "common/common_types.h" -#include "common/utf8.h" - -// is start of UTF sequence -inline bool isutf(char c) { - return (c & 0xC0) != 0x80; -} - -static const u32 offsetsFromUTF8[6] = { - 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL -}; - -static const u8 trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5, -}; - -/* returns length of next utf-8 sequence */ -int u8_seqlen(const char *s) -{ - return trailingBytesForUTF8[(unsigned int)(unsigned char)s[0]] + 1; -} - -/* conversions without error checking - only works for valid UTF-8, i.e. no 5- or 6-byte sequences - srcsz = source size in bytes, or -1 if 0-terminated - sz = dest size in # of wide characters - - returns # characters converted - dest will always be L'\0'-terminated, even if there isn't enough room - for all the characters. - if sz = srcsz+1 (i.e. 4*srcsz+4 bytes), there will always be enough space. -*/ -int u8_toucs(u32 *dest, int sz, const char *src, int srcsz) -{ - u32 ch; - const char *src_end = src + srcsz; - int nb; - int i=0; - - while (i < sz-1) { - nb = trailingBytesForUTF8[(unsigned char)*src]; - if (srcsz == -1) { - if (*src == 0) - goto done_toucs; - } - else { - if (src + nb >= src_end) - goto done_toucs; - } - ch = 0; - switch (nb) { - /* these fall through deliberately */ - case 3: ch += (unsigned char)*src++; ch <<= 6; - case 2: ch += (unsigned char)*src++; ch <<= 6; - case 1: ch += (unsigned char)*src++; ch <<= 6; - case 0: ch += (unsigned char)*src++; - } - ch -= offsetsFromUTF8[nb]; - dest[i++] = ch; - } - done_toucs: - dest[i] = 0; - return i; -} - -/* srcsz = number of source characters, or -1 if 0-terminated - sz = size of dest buffer in bytes - - returns # characters converted - dest will only be '\0'-terminated if there is enough space. this is - for consistency; imagine there are 2 bytes of space left, but the next - character requires 3 bytes. in this case we could NUL-terminate, but in - general we can't when there's insufficient space. therefore this function - only NUL-terminates if all the characters fit, and there's space for - the NUL as well. - the destination string will never be bigger than the source string. -*/ -int u8_toutf8(char *dest, int sz, u32 *src, int srcsz) -{ - u32 ch; - int i = 0; - char *dest_end = dest + sz; - - while (srcsz<0 ? src[i]!=0 : i < srcsz) { - ch = src[i]; - if (ch < 0x80) { - if (dest >= dest_end) - return i; - *dest++ = (char)ch; - } - else if (ch < 0x800) { - if (dest >= dest_end-1) - return i; - *dest++ = (ch>>6) | 0xC0; - *dest++ = (ch & 0x3F) | 0x80; - } - else if (ch < 0x10000) { - if (dest >= dest_end-2) - return i; - *dest++ = (ch>>12) | 0xE0; - *dest++ = ((ch>>6) & 0x3F) | 0x80; - *dest++ = (ch & 0x3F) | 0x80; - } - else if (ch < 0x110000) { - if (dest >= dest_end-3) - return i; - *dest++ = (ch>>18) | 0xF0; - *dest++ = ((ch>>12) & 0x3F) | 0x80; - *dest++ = ((ch>>6) & 0x3F) | 0x80; - *dest++ = (ch & 0x3F) | 0x80; - } - i++; - } - if (dest < dest_end) - *dest = '\0'; - return i; -} - -int u8_wc_toutf8(char *dest, u32 ch) -{ - if (ch < 0x80) { - dest[0] = (char)ch; - return 1; - } - if (ch < 0x800) { - dest[0] = (ch>>6) | 0xC0; - dest[1] = (ch & 0x3F) | 0x80; - return 2; - } - if (ch < 0x10000) { - dest[0] = (ch>>12) | 0xE0; - dest[1] = ((ch>>6) & 0x3F) | 0x80; - dest[2] = (ch & 0x3F) | 0x80; - return 3; - } - if (ch < 0x110000) { - dest[0] = (ch>>18) | 0xF0; - dest[1] = ((ch>>12) & 0x3F) | 0x80; - dest[2] = ((ch>>6) & 0x3F) | 0x80; - dest[3] = (ch & 0x3F) | 0x80; - return 4; - } - return 0; -} - -/* charnum => byte offset */ -int u8_offset(const char *str, int charnum) -{ - int offs=0; - - while (charnum > 0 && str[offs]) { - (void)(isutf(str[++offs]) || isutf(str[++offs]) || - isutf(str[++offs]) || ++offs); - charnum--; - } - return offs; -} - -/* byte offset => charnum */ -int u8_charnum(const char *s, int offset) -{ - int charnum = 0, offs=0; - - while (offs < offset && s[offs]) { - (void)(isutf(s[++offs]) || isutf(s[++offs]) || - isutf(s[++offs]) || ++offs); - charnum++; - } - return charnum; -} - -/* number of characters */ -int u8_strlen(const char *s) -{ - int count = 0; - int i = 0; - - while (u8_nextchar(s, &i) != 0) - count++; - - return count; -} - -/* reads the next utf-8 sequence out of a string, updating an index */ -u32 u8_nextchar(const char *s, int *i) -{ - u32 ch = 0; - int sz = 0; - - do { - ch <<= 6; - ch += (unsigned char)s[(*i)++]; - sz++; - } while (s[*i] && !isutf(s[*i])); - ch -= offsetsFromUTF8[sz-1]; - - return ch; -} - -void u8_inc(const char *s, int *i) -{ - (void)(isutf(s[++(*i)]) || isutf(s[++(*i)]) || - isutf(s[++(*i)]) || ++(*i)); -} - -void u8_dec(const char *s, int *i) -{ - (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || - isutf(s[--(*i)]) || --(*i)); -} - -int octal_digit(char c) -{ - return (c >= '0' && c <= '7'); -} - -int hex_digit(char c) -{ - return ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f')); -} - -/* assumes that src points to the character after a backslash - returns number of input characters processed */ -int u8_read_escape_sequence(const char *str, u32 *dest) -{ - u32 ch; - char digs[9]="\0\0\0\0\0\0\0\0"; - int dno=0, i=1; - - ch = (u32)str[0]; /* take literal character */ - if (str[0] == 'n') - ch = L'\n'; - else if (str[0] == 't') - ch = L'\t'; - else if (str[0] == 'r') - ch = L'\r'; - else if (str[0] == 'b') - ch = L'\b'; - else if (str[0] == 'f') - ch = L'\f'; - else if (str[0] == 'v') - ch = L'\v'; - else if (str[0] == 'a') - ch = L'\a'; - else if (octal_digit(str[0])) { - i = 0; - do { - digs[dno++] = str[i++]; - } while (octal_digit(str[i]) && dno < 3); - ch = strtol(digs, nullptr, 8); - } - else if (str[0] == 'x') { - while (hex_digit(str[i]) && dno < 2) { - digs[dno++] = str[i++]; - } - if (dno > 0) - ch = strtol(digs, nullptr, 16); - } - else if (str[0] == 'u') { - while (hex_digit(str[i]) && dno < 4) { - digs[dno++] = str[i++]; - } - if (dno > 0) - ch = strtol(digs, nullptr, 16); - } - else if (str[0] == 'U') { - while (hex_digit(str[i]) && dno < 8) { - digs[dno++] = str[i++]; - } - if (dno > 0) - ch = strtol(digs, nullptr, 16); - } - *dest = ch; - - return i; -} - -/* convert a string with literal \uxxxx or \Uxxxxxxxx characters to UTF-8 - example: u8_unescape(mybuf, 256, "hello\\u220e") - note the double backslash is needed if called on a C string literal */ -int u8_unescape(char *buf, int sz, char *src) -{ - int c=0, amt; - u32 ch; - char temp[4]; - - while (*src && c < sz) { - if (*src == '\\') { - src++; - amt = u8_read_escape_sequence(src, &ch); - } - else { - ch = (u32)*src; - amt = 1; - } - src += amt; - amt = u8_wc_toutf8(temp, ch); - if (amt > sz-c) - break; - memcpy(&buf[c], temp, amt); - c += amt; - } - if (c < sz) - buf[c] = '\0'; - return c; -} - -const char *u8_strchr(const char *s, u32 ch, int *charn) -{ - int i = 0, lasti=0; - u32 c; - - *charn = 0; - while (s[i]) { - c = u8_nextchar(s, &i); - if (c == ch) { - return &s[lasti]; - } - lasti = i; - (*charn)++; - } - return nullptr; -} - -const char *u8_memchr(const char *s, u32 ch, size_t sz, int *charn) -{ - u32 i = 0, lasti=0; - u32 c; - int csz; - - *charn = 0; - while (i < sz) { - c = csz = 0; - do { - c <<= 6; - c += (unsigned char)s[i++]; - csz++; - } while (i < sz && !isutf(s[i])); - c -= offsetsFromUTF8[csz-1]; - - if (c == ch) { - return &s[lasti]; - } - lasti = i; - (*charn)++; - } - return nullptr; -} - -int u8_is_locale_utf8(const char *locale) -{ - /* this code based on libutf8 */ - const char* cp = locale; - - for (; *cp != '\0' && *cp != '@' && *cp != '+' && *cp != ','; cp++) { - if (*cp == '.') { - const char* encoding = ++cp; - for (; *cp != '\0' && *cp != '@' && *cp != '+' && *cp != ','; cp++) - ; - if ((cp-encoding == 5 && !strncmp(encoding, "UTF-8", 5)) - || (cp-encoding == 4 && !strncmp(encoding, "utf8", 4))) - return 1; /* it's UTF-8 */ - break; - } - } - return 0; -} - -int UTF8StringNonASCIICount(const char *utf8string) { - UTF8 utf(utf8string); - int count = 0; - while (!utf.end()) { - int c = utf.next(); - if (c > 127) - ++count; - } - return count; -} - -bool UTF8StringHasNonASCII(const char *utf8string) { - return UTF8StringNonASCIICount(utf8string) > 0; -} - -#ifdef _WIN32 - -std::string ConvertWStringToUTF8(const wchar_t *wstr) { - int len = (int)wcslen(wstr); - int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr, len, 0, 0, nullptr, nullptr); - std::string s; - s.resize(size); - if (size > 0) { - WideCharToMultiByte(CP_UTF8, 0, wstr, len, &s[0], size, nullptr, nullptr); - } - return s; -} - -std::string ConvertWStringToUTF8(const std::wstring &wstr) { - int len = (int)wstr.size(); - int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, 0, 0, nullptr, nullptr); - std::string s; - s.resize(size); - if (size > 0) { - WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, &s[0], size, nullptr, nullptr); - } - return s; -} - -void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const std::string &source) { - int len = (int)source.size(); - int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, nullptr, 0); - MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, dest, std::min((int)destSize, size)); -} - -std::wstring ConvertUTF8ToWString(const std::string &source) { - int len = (int)source.size(); - int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, nullptr, 0); - std::wstring str; - str.resize(size); - if (size > 0) { - MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, &str[0], size); - } - return str; -} - -#endif diff --git a/src/common/utf8.h b/src/common/utf8.h deleted file mode 100644 index a6e84913b..000000000 --- a/src/common/utf8.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - Basic UTF-8 manipulation routines - by Jeff Bezanson - placed in the public domain Fall 2005 - - This code is designed to provide the utilities you need to manipulate - UTF-8 as an internal string encoding. These functions do not perform the - error checking normally needed when handling UTF-8 data, so if you happen - to be from the Unicode Consortium you will want to flay me alive. - I do this because error checking can be performed at the boundaries (I/O), - with these routines reserved for higher performance on data known to be - valid. -*/ - -// Further modified, and C++ stuff added, by hrydgard@gmail.com. - -#pragma once - -#include "common/common_types.h" -#include <string> - -u32 u8_nextchar(const char *s, int *i); -int u8_wc_toutf8(char *dest, u32 ch); -int u8_strlen(const char *s); - -class UTF8 { -public: - static const u32 INVALID = (u32)-1; - UTF8(const char *c) : c_(c), index_(0) {} - bool end() const { return c_[index_] == 0; } - u32 next() { - return u8_nextchar(c_, &index_); - } - u32 peek() { - int tempIndex = index_; - return u8_nextchar(c_, &tempIndex); - } - int length() const { - return u8_strlen(c_); - } - int byteIndex() const { - return index_; - } - static int encode(char *dest, u32 ch) { - return u8_wc_toutf8(dest, ch); - } - -private: - const char *c_; - int index_; -}; - -int UTF8StringNonASCIICount(const char *utf8string); - -bool UTF8StringHasNonASCII(const char *utf8string); - - -// UTF8 to Win32 UTF-16 -// Should be used when calling Win32 api calls -#ifdef _WIN32 - -std::string ConvertWStringToUTF8(const std::wstring &wstr); -std::string ConvertWStringToUTF8(const wchar_t *wstr); -void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const std::string &source); -std::wstring ConvertUTF8ToWString(const std::string &source); - -#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0ab0e440c..212da25c5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -35,9 +35,10 @@ set(SRCS hle/service/am_app.cpp hle/service/am_net.cpp hle/service/am_sys.cpp - hle/service/apt_a.cpp - hle/service/apt_s.cpp - hle/service/apt_u.cpp + hle/service/apt/apt.cpp + hle/service/apt/apt_a.cpp + hle/service/apt/apt_s.cpp + hle/service/apt/apt_u.cpp hle/service/boss_p.cpp hle/service/boss_u.cpp hle/service/cam_u.cpp @@ -71,9 +72,10 @@ set(SRCS hle/service/ns_s.cpp hle/service/nwm_uds.cpp hle/service/pm_app.cpp - hle/service/ptm_play.cpp - hle/service/ptm_u.cpp - hle/service/ptm_sysm.cpp + hle/service/ptm/ptm.cpp + hle/service/ptm/ptm_play.cpp + hle/service/ptm/ptm_u.cpp + hle/service/ptm/ptm_sysm.cpp hle/service/service.cpp hle/service/soc_u.cpp hle/service/srv.cpp @@ -140,9 +142,10 @@ set(HEADERS hle/service/am_app.h hle/service/am_net.h hle/service/am_sys.h - hle/service/apt_a.h - hle/service/apt_s.h - hle/service/apt_u.h + hle/service/apt/apt.h + hle/service/apt/apt_a.h + hle/service/apt/apt_s.h + hle/service/apt/apt_u.h hle/service/boss_p.h hle/service/boss_u.h hle/service/cam_u.h @@ -176,9 +179,10 @@ set(HEADERS hle/service/ns_s.h hle/service/nwm_uds.h hle/service/pm_app.h - hle/service/ptm_play.h - hle/service/ptm_u.h - hle/service/ptm_sysm.h + hle/service/ptm/ptm.h + hle/service/ptm/ptm_play.h + hle/service/ptm/ptm_u.h + hle/service/ptm/ptm_sysm.h hle/service/service.h hle/service/soc_u.h hle/service/srv.h diff --git a/src/core/arm/disassembler/arm_disasm.cpp b/src/core/arm/disassembler/arm_disasm.cpp index f7c7451e9..913dc1454 100644 --- a/src/core/arm/disassembler/arm_disasm.cpp +++ b/src/core/arm/disassembler/arm_disasm.cpp @@ -24,7 +24,7 @@ static const char *cond_names[] = { "RESERVED" }; -const char *opcode_names[] = { +static const char *opcode_names[] = { "invalid", "undefined", "adc", @@ -131,7 +131,7 @@ static const char *shift_names[] = { "ROR" }; -static const char* cond_to_str(int cond) { +static const char* cond_to_str(uint32_t cond) { return cond_names[cond]; } @@ -337,8 +337,9 @@ std::string ARM_Disasm::DisassembleBX(uint32_t insn) std::string ARM_Disasm::DisassembleBKPT(uint32_t insn) { + uint8_t cond = (insn >> 28) & 0xf; uint32_t immed = (((insn >> 8) & 0xfff) << 4) | (insn & 0xf); - return Common::StringFromFormat("bkpt\t#%d", immed); + return Common::StringFromFormat("bkpt%s\t#%d", cond_to_str(cond), immed); } std::string ARM_Disasm::DisassembleCLZ(uint32_t insn) @@ -351,7 +352,6 @@ std::string ARM_Disasm::DisassembleCLZ(uint32_t insn) std::string ARM_Disasm::DisassembleMemblock(Opcode opcode, uint32_t insn) { - std::string tmp_reg; std::string tmp_list; uint8_t cond = (insn >> 28) & 0xf; diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index b691ffbc3..2f72f5077 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -9,6 +9,7 @@ #include <unordered_map> #include "common/logging/log.h" +#include "common/profiler.h" #include "core/mem_map.h" #include "core/hle/hle.h" @@ -20,6 +21,9 @@ #include "core/arm/skyeye_common/armmmu.h" #include "core/arm/skyeye_common/vfp/vfp.h" +Common::Profiling::TimingCategory profile_execute("DynCom::Execute"); +Common::Profiling::TimingCategory profile_decode("DynCom::Decode"); + enum { COND = (1 << 0), NON_BRANCH = (1 << 1), @@ -3569,6 +3573,8 @@ typedef struct instruction_set_encoding_item ISEITEM; extern const ISEITEM arm_instruction[]; static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, addr_t addr) { + Common::Profiling::ScopeTimer timer_decode(profile_decode); + // Decode instruction, get index // Allocate memory and init InsCream // Go on next, until terminal instruction @@ -3641,6 +3647,8 @@ static bool InAPrivilegedMode(ARMul_State* core) { } unsigned InterpreterMainLoop(ARMul_State* state) { + Common::Profiling::ScopeTimer timer_execute(profile_execute); + #undef RM #undef RS @@ -4354,6 +4362,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { cpu->Reg[14] = Memory::Read32(addr); else cpu->Reg_usr[1] = Memory::Read32(addr); + + addr += 4; } } else if (!BIT(inst, 22)) { for(int i = 0; i < 16; i++ ){ @@ -4422,12 +4432,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { inst_cream->get_addr(cpu, inst_cream->inst, addr, 1); unsigned int value = Memory::Read32(addr); - if (BIT(CP15_REG(CP15_CONTROL), 22) == 1) - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - else { - value = ROTATE_RIGHT_32(value,(8*(addr&0x3))); - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - } + cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; if (BITS(inst_cream->inst, 12, 15) == 15) { // For armv5t, should enter thumb when bits[0] is non-zero. @@ -4450,12 +4455,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { inst_cream->get_addr(cpu, inst_cream->inst, addr, 1); unsigned int value = Memory::Read32(addr); - if (BIT(CP15_REG(CP15_CONTROL), 22) == 1) - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - else { - value = ROTATE_RIGHT_32(value,(8*(addr&0x3))); - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - } + cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; if (BITS(inst_cream->inst, 12, 15) == 15) { // For armv5t, should enter thumb when bits[0] is non-zero. @@ -4699,11 +4699,6 @@ unsigned InterpreterMainLoop(ARMul_State* state) { unsigned int value = Memory::Read32(addr); cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - if (BIT(CP15_REG(CP15_CONTROL), 22) == 1) - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = value; - else - cpu->Reg[BITS(inst_cream->inst, 12, 15)] = ROTATE_RIGHT_32(value,(8*(addr&0x3))) ; - if (BITS(inst_cream->inst, 12, 15) == 15) { INC_PC(sizeof(ldst_inst)); goto DISPATCH; @@ -4724,9 +4719,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { DEBUG_MSG; } else { if (inst_cream->cp_num == 15) { - if(CRn == 0 && OPCODE_2 == 0 && CRm == 0) { - CP15_REG(CP15_MAIN_ID) = RD; - } else if(CRn == 1 && CRm == 0 && OPCODE_2 == 0) { + if (CRn == 1 && CRm == 0 && OPCODE_2 == 0) { CP15_REG(CP15_CONTROL) = RD; } else if (CRn == 1 && CRm == 0 && OPCODE_2 == 1) { CP15_REG(CP15_AUXILIARY_CONTROL) = RD; @@ -5983,54 +5976,51 @@ unsigned InterpreterMainLoop(ARMul_State* state) { ldst_inst* inst_cream = (ldst_inst*)inst_base->component; unsigned int inst = inst_cream->inst; - int i; unsigned int Rn = BITS(inst, 16, 19); unsigned int old_RN = cpu->Reg[Rn]; inst_cream->get_addr(cpu, inst_cream->inst, addr, 0); if (BIT(inst_cream->inst, 22) == 1) { - for (i = 0; i < 13; i++) { - if(BIT(inst_cream->inst, i)) { + for (int i = 0; i < 13; i++) { + if (BIT(inst_cream->inst, i)) { Memory::Write32(addr, cpu->Reg[i]); addr += 4; } } if (BIT(inst_cream->inst, 13)) { - if (cpu->Mode == USER32MODE) { - Memory::Write32(addr, cpu->Reg[i]); - addr += 4; - } else { + if (cpu->Mode == USER32MODE) + Memory::Write32(addr, cpu->Reg[13]); + else Memory::Write32(addr, cpu->Reg_usr[0]); - addr += 4; - } + + addr += 4; } if (BIT(inst_cream->inst, 14)) { - if (cpu->Mode == USER32MODE) { - Memory::Write32(addr, cpu->Reg[i]); - addr += 4; - } else { + if (cpu->Mode == USER32MODE) + Memory::Write32(addr, cpu->Reg[14]); + else Memory::Write32(addr, cpu->Reg_usr[1]); - addr += 4; - } + + addr += 4; } if (BIT(inst_cream->inst, 15)) { Memory::Write32(addr, cpu->Reg_usr[1] + 8); } } else { - for( i = 0; i < 15; i++ ) { - if(BIT(inst_cream->inst, i)) { - if(i == Rn) + for (int i = 0; i < 15; i++) { + if (BIT(inst_cream->inst, i)) { + if (i == Rn) Memory::Write32(addr, old_RN); else Memory::Write32(addr, cpu->Reg[i]); + addr += 4; } } // Check PC reg - if(BIT(inst_cream->inst, i)) { + if (BIT(inst_cream->inst, 15)) Memory::Write32(addr, cpu->Reg_usr[1] + 8); - } } } cpu->Reg[15] += GET_INST_SIZE(cpu); diff --git a/src/core/arm/interpreter/arminit.cpp b/src/core/arm/interpreter/arminit.cpp index abafe226e..4ac827e0a 100644 --- a/src/core/arm/interpreter/arminit.cpp +++ b/src/core/arm/interpreter/arminit.cpp @@ -58,11 +58,7 @@ void ARMul_SelectProcessor(ARMul_State* state, unsigned properties) state->is_v4 = (properties & (ARM_v4_Prop | ARM_v5_Prop)) != 0; state->is_v5 = (properties & ARM_v5_Prop) != 0; state->is_v5e = (properties & ARM_v5e_Prop) != 0; - state->is_XScale = (properties & ARM_XScale_Prop) != 0; - state->is_iWMMXt = (properties & ARM_iWMMXt_Prop) != 0; state->is_v6 = (properties & ARM_v6_Prop) != 0; - state->is_ep9312 = (properties & ARM_ep9312_Prop) != 0; - state->is_pxa27x = (properties & ARM_PXA27X_Prop) != 0; state->is_v7 = (properties & ARM_v7_Prop) != 0; // Only initialse the coprocessor support once we diff --git a/src/core/arm/skyeye_common/armdefs.h b/src/core/arm/skyeye_common/armdefs.h index 070fcf7dc..16f3ac86c 100644 --- a/src/core/arm/skyeye_common/armdefs.h +++ b/src/core/arm/skyeye_common/armdefs.h @@ -185,10 +185,6 @@ So, if lateabtSig=1, then it means Late Abort Model(Base Updated Abort Model) bool is_v5e; // Are we emulating a v5e architecture? bool is_v6; // Are we emulating a v6 architecture? bool is_v7; // Are we emulating a v7 architecture? - bool is_XScale; // Are we emulating an XScale architecture? - bool is_iWMMXt; // Are we emulating an iWMMXt co-processor? - bool is_ep9312; // Are we emulating a Cirrus Maverick co-processor? - bool is_pxa27x; // Are we emulating a Intel PXA27x co-processor? // ARM_ARM A2-18 // 0 Base Restored Abort Model, 1 the Early Abort Model, 2 Base Updated Abort Model @@ -211,20 +207,11 @@ So, if lateabtSig=1, then it means Late Abort Model(Base Updated Abort Model) \***************************************************************************/ enum { - ARM_Nexec_Prop = 0x02, - ARM_Debug_Prop = 0x10, - ARM_Isync_Prop = ARM_Debug_Prop, - ARM_Lock_Prop = 0x20, - ARM_v4_Prop = 0x40, - ARM_v5_Prop = 0x80, - ARM_v6_Prop = 0xc0, - - ARM_v5e_Prop = 0x100, - ARM_XScale_Prop = 0x200, - ARM_ep9312_Prop = 0x400, - ARM_iWMMXt_Prop = 0x800, - ARM_PXA27X_Prop = 0x1000, - ARM_v7_Prop = 0x2000, + ARM_v4_Prop = 0x01, + ARM_v5_Prop = 0x02, + ARM_v5e_Prop = 0x04, + ARM_v6_Prop = 0x08, + ARM_v7_Prop = 0x10, }; /***************************************************************************\ diff --git a/src/core/arm/skyeye_common/vfp/vfp_helper.h b/src/core/arm/skyeye_common/vfp/vfp_helper.h index 75d860e95..5d1b4e53f 100644 --- a/src/core/arm/skyeye_common/vfp/vfp_helper.h +++ b/src/core/arm/skyeye_common/vfp/vfp_helper.h @@ -144,8 +144,8 @@ static inline void mul64to128(u64* resh, u64* resl, u64 n, u64 m) u32 nh, nl, mh, ml; u64 rh, rma, rmb, rl; - nl = n; - ml = m; + nl = static_cast<u32>(n); + ml = static_cast<u32>(m); rl = (u64)nl * ml; nh = n >> 32; diff --git a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp index 1a05ef8c1..d76d37fd4 100644 --- a/src/core/arm/skyeye_common/vfp/vfpdouble.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpdouble.cpp @@ -661,8 +661,8 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 if ((rem + incr) < rem && d < 0xffffffff) d += 1; - if (d > (0x7fffffff + (vdm.sign != 0))) { - d = (0x7fffffff + (vdm.sign != 0)); + if (d > (0x7fffffffU + (vdm.sign != 0))) { + d = (0x7fffffffU + (vdm.sign != 0)); exceptions |= FPSCR_IOC; } else if (rem) exceptions |= FPSCR_IXC; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index cabe2a074..6f716b1ca 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -147,7 +147,7 @@ void RestoreRegisterEvent(int event_type, const char* name, TimedCallback callba void UnregisterAllEvents() { if (first) - PanicAlert("Cannot unregister events with events pending"); + LOG_ERROR(Core_Timing, "Cannot unregister events with events pending"); event_types.clear(); } @@ -535,7 +535,7 @@ std::string GetScheduledEventsSummary() { while (event) { unsigned int t = event->type; if (t >= event_types.size()) - PanicAlert("Invalid event type"); // %i", t); + LOG_ERROR(Core_Timing, "Invalid event type"); // %i", t); const char* name = event_types[event->type].name; if (!name) name = "[unknown]"; diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index c2a5d641a..25c94cd26 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -6,9 +6,9 @@ #include "common/common_types.h" #include "common/file_util.h" +#include "common/make_unique.h" #include "core/file_sys/archive_systemsavedata.h" -#include "core/file_sys/disk_archive.h" #include "core/hle/service/fs/archive.h" #include "core/settings.h" @@ -17,9 +17,11 @@ namespace FileSys { -static std::string GetSystemSaveDataPath(const std::string& mount_point, u64 save_id) { - u32 save_high = static_cast<u32>((save_id >> 32) & 0xFFFFFFFF); - u32 save_low = static_cast<u32>(save_id & 0xFFFFFFFF); +static std::string GetSystemSaveDataPath(const std::string& mount_point, const Path& path) { + std::vector<u8> vec_data = path.AsBinary(); + const u32* data = reinterpret_cast<const u32*>(vec_data.data()); + u32 save_low = data[1]; + u32 save_high = data[0]; return Common::StringFromFormat("%s%08X/%08X/", mount_point.c_str(), save_low, save_high); } @@ -27,18 +29,25 @@ static std::string GetSystemSaveDataContainerPath(const std::string& mount_point return Common::StringFromFormat("%sdata/%s/sysdata/", mount_point.c_str(), SYSTEM_ID.c_str()); } -Archive_SystemSaveData::Archive_SystemSaveData(const std::string& mount_point, u64 save_id) - : DiskArchive(GetSystemSaveDataPath(GetSystemSaveDataContainerPath(mount_point), save_id)) { - LOG_INFO(Service_FS, "Directory %s set as SystemSaveData.", this->mount_point.c_str()); +ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path) + : base_path(GetSystemSaveDataContainerPath(nand_path)) { } -bool Archive_SystemSaveData::Initialize() { - if (!FileUtil::CreateFullPath(mount_point)) { - LOG_ERROR(Service_FS, "Unable to create SystemSaveData path."); - return false; +ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path) { + std::string fullpath = GetSystemSaveDataPath(base_path, path); + if (!FileUtil::Exists(fullpath)) { + // TODO(Subv): Check error code, this one is probably wrong + return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, + ErrorSummary::InvalidState, ErrorLevel::Status); } + auto archive = Common::make_unique<DiskArchive>(fullpath); + return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); +} - return true; +ResultCode ArchiveFactory_SystemSaveData::Format(const Path& path) { + std::string fullpath = GetSystemSaveDataPath(base_path, path); + FileUtil::CreateFullPath(fullpath); + return RESULT_SUCCESS; } } // namespace FileSys diff --git a/src/core/file_sys/archive_systemsavedata.h b/src/core/file_sys/archive_systemsavedata.h index c8f5845ca..556a2a488 100644 --- a/src/core/file_sys/archive_systemsavedata.h +++ b/src/core/file_sys/archive_systemsavedata.h @@ -15,17 +15,17 @@ namespace FileSys { /// File system interface to the SystemSaveData archive -class Archive_SystemSaveData final : public DiskArchive { +class ArchiveFactory_SystemSaveData final : public ArchiveFactory { public: - Archive_SystemSaveData(const std::string& mount_point, u64 save_id); + ArchiveFactory_SystemSaveData(const std::string& mount_point); - /** - * Initialize the archive. - * @return true if it initialized successfully - */ - bool Initialize(); + ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; std::string GetName() const override { return "SystemSaveData"; } + +private: + std::string base_path; }; } // namespace FileSys diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index b0066e15e..1aaeaa9c9 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp @@ -4,6 +4,8 @@ #include <vector> +#include "common/profiler.h" + #include "core/arm/arm_interface.h" #include "core/mem_map.h" #include "core/hle/hle.h" @@ -11,14 +13,13 @@ #include "core/hle/shared_page.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/service.h" -#include "core/hle/service/fs/archive.h" -#include "core/hle/service/cfg/cfg.h" -#include "core/hle/service/hid/hid.h" //////////////////////////////////////////////////////////////////////////////////////////////////// namespace HLE { +Common::Profiling::TimingCategory profiler_svc("SVC Calls"); + static std::vector<ModuleDef> g_module_db; bool g_reschedule = false; ///< If true, immediately reschedules the CPU to a new thread @@ -33,6 +34,8 @@ static const FunctionDef* GetSVCInfo(u32 opcode) { } void CallSVC(u32 opcode) { + Common::Profiling::ScopeTimer timer_svc(profiler_svc); + const FunctionDef *info = GetSVCInfo(opcode); if (!info) { @@ -70,9 +73,6 @@ static void RegisterAllModules() { void Init() { Service::Init(); - Service::FS::ArchiveInit(); - Service::CFG::CFGInit(); - Service::HID::HIDInit(); RegisterAllModules(); @@ -83,9 +83,6 @@ void Init() { } void Shutdown() { - Service::HID::HIDShutdown(); - Service::CFG::CFGShutdown(); - Service::FS::ArchiveShutdown(); Service::Shutdown(); g_module_db.clear(); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index f8c834a8d..be1aed615 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -7,6 +7,7 @@ #include <vector> #include "common/common.h" +#include "common/math_util.h" #include "common/thread_queue_list.h" #include "core/arm/arm_interface.h" @@ -339,7 +340,7 @@ static void DebugThreadQueue() { ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, u32 arg, s32 processor_id, VAddr stack_top) { if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { - s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); + s32 new_priority = MathUtil::Clamp<s32>(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", name.c_str(), priority, new_priority); // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm @@ -387,7 +388,7 @@ static void ClampPriority(const Thread* thread, s32* priority) { if (*priority < THREADPRIO_HIGHEST || *priority > THREADPRIO_LOWEST) { DEBUG_ASSERT_MSG(false, "Application passed an out of range priority. An error should be returned."); - s32 new_priority = CLAMP(*priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); + s32 new_priority = MathUtil::Clamp<s32>(*priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", thread->name.c_str(), *priority, new_priority); // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 0e391fe2d..3648a168b 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -208,11 +208,11 @@ union ResultCode { } }; -inline bool operator==(const ResultCode a, const ResultCode b) { +inline bool operator==(const ResultCode& a, const ResultCode& b) { return a.raw == b.raw; } -inline bool operator!=(const ResultCode a, const ResultCode b) { +inline bool operator!=(const ResultCode& a, const ResultCode& b) { return a.raw != b.raw; } diff --git a/src/core/hle/service/am_sys.cpp b/src/core/hle/service/am_sys.cpp index 7ab89569f..b244190a2 100644 --- a/src/core/hle/service/am_sys.cpp +++ b/src/core/hle/service/am_sys.cpp @@ -5,19 +5,56 @@ #include "core/hle/hle.h" #include "core/hle/service/am_sys.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace AM_SYS - namespace AM_SYS { -// Empty arrays are illegal -- commented out until an entry is added. -//const Interface::FunctionInfo FunctionTable[] = { }; +/** + * Gets the number of installed titles in the requested media type + * Inputs: + * 0: Command header (0x00010040) + * 1: Media type to load the titles from + * Outputs: + * 1: Result, 0 on success, otherwise error code + * 2: The number of titles in the requested media type + */ +static void TitleIDListGetTotal(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 media_type = cmd_buff[1] & 0xFF; + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + LOG_WARNING(Service_CFG, "(STUBBED) media_type %u", media_type); +} + +/** + * Loads information about the desired number of titles from the desired media type into an array + * Inputs: + * 0: Command header (0x00020082) + * 1: The maximum number of titles to load + * 2: Media type to load the titles from + * 3: Descriptor of the output buffer pointer + * 4: Address of the output buffer + * Outputs: + * 1: Result, 0 on success, otherwise error code + * 2: The number of titles loaded from the requested media type + */ +static void GetTitleIDList(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 num_titles = cmd_buff[1]; + u32 media_type = cmd_buff[2] & 0xFF; + u32 addr = cmd_buff[4]; + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + LOG_WARNING(Service_CFG, "(STUBBED) Requested %u titles from media type %u", num_titles, media_type); +} -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class +const Interface::FunctionInfo FunctionTable[] = { + {0x00010040, TitleIDListGetTotal, "TitleIDListGetTotal"}, + {0x00020082, GetTitleIDList, "GetTitleIDList"}, +}; Interface::Interface() { - //Register(FunctionTable); + Register(FunctionTable); } } // namespace diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp new file mode 100644 index 000000000..5971f860b --- /dev/null +++ b/src/core/hle/service/apt/apt.cpp @@ -0,0 +1,285 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/file_util.h" + +#include "core/hle/service/service.h" +#include "core/hle/service/apt/apt.h" +#include "core/hle/service/apt/apt_a.h" +#include "core/hle/service/apt/apt_s.h" +#include "core/hle/service/apt/apt_u.h" + +#include "core/hle/hle.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/kernel/thread.h" + +namespace Service { +namespace APT { + +// Address used for shared font (as observed on HW) +// TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via +// https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any +// address other than 0x18000000 due to internal pointers in the shared font dump that would need to +// be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then +// correctly mapping it in Citra, however we still do not understand how the mapping is determined. +static const VAddr SHARED_FONT_VADDR = 0x18000000; + +/// Handle to shared memory region designated to for shared system font +static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; + +static Kernel::SharedPtr<Kernel::Mutex> lock; +static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event +static Kernel::SharedPtr<Kernel::Event> pause_event = 0; ///< APT pause event +static std::vector<u8> shared_font; + +void Initialize(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(pause_event).MoveFrom(); + + // TODO(bunnei): Check if these events are cleared/signaled every time Initialize is called. + notification_event->Clear(); + pause_event->Signal(); // Fire start event + + ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); + lock->Release(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error +} + +void GetSharedFont(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + if (!shared_font.empty()) { + // TODO(bunnei): This function shouldn't copy the shared font every time it's called. + // Instead, it should probably map the shared font as RO memory. We don't currently have + // an easy way to do this, but the copy should be sufficient for now. + memcpy(Memory::GetPointer(SHARED_FONT_VADDR), shared_font.data(), shared_font.size()); + + cmd_buff[0] = 0x00440082; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = SHARED_FONT_VADDR; + cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); + } else { + cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware) + LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT); + } +} + +void NotifyToWait(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. + pause_event->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); +} + +void GetLockHandle(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + // Not sure what these parameters are used for, but retail apps check that they are 0 after + // GetLockHandle has been called. + cmd_buff[2] = 0; + cmd_buff[3] = 0; + cmd_buff[4] = 0; + + cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); + LOG_TRACE(Service_APT, "called handle=0x%08X", cmd_buff[5]); +} + +void Enable(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 unk = cmd_buff[1]; // TODO(bunnei): What is this field used for? + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); +} + +void GetAppletManInfo(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 unk = cmd_buff[1]; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; + cmd_buff[3] = 0; + cmd_buff[4] = static_cast<u32>(AppID::HomeMenu); // Home menu AppID + cmd_buff[5] = static_cast<u32>(AppID::Application); // TODO(purpasmart96): Do this correctly + + LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); +} + +void IsRegistered(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 1; // Set to registered + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); +} + +void InquireNotification(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); +} + +void SendParameter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 src_app_id = cmd_buff[1]; + u32 dst_app_id = cmd_buff[2]; + u32 signal_type = cmd_buff[3]; + u32 buffer_size = cmd_buff[4]; + u32 value = cmd_buff[5]; + u32 handle = cmd_buff[6]; + u32 size = cmd_buff[7]; + u32 in_param_buffer_ptr = cmd_buff[8]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_APT, "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," + "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", + src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, in_param_buffer_ptr); +} + +void ReceiveParameter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + u32 buffer_size = cmd_buff[2]; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; + cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type + cmd_buff[4] = 0x10; // Parameter buffer size (16) + cmd_buff[5] = 0; + cmd_buff[6] = 0; + cmd_buff[7] = 0; + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); +} + +void GlanceParameter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + u32 buffer_size = cmd_buff[2]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; + cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type + cmd_buff[4] = 0x10; // Parameter buffer size (16) + cmd_buff[5] = 0; + cmd_buff[6] = 0; + cmd_buff[7] = 0; + + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); +} + +void CancelParameter(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 flag1 = cmd_buff[1]; + u32 unk = cmd_buff[2]; + u32 flag2 = cmd_buff[3]; + u32 app_id = cmd_buff[4]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 1; // Set to Success + + LOG_WARNING(Service_APT, "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", + flag1, unk, flag2, app_id); +} + +void AppletUtility(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // These are from 3dbrew - I'm not really sure what they're used for. + u32 unk = cmd_buff[1]; + u32 buffer1_size = cmd_buff[2]; + u32 buffer2_size = cmd_buff[3]; + u32 buffer1_addr = cmd_buff[5]; + u32 buffer2_addr = cmd_buff[65]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X, buffer1_size=0x%08x, buffer2_size=0x%08x, " + "buffer1_addr=0x%08x, buffer2_addr=0x%08x", unk, buffer1_size, buffer2_size, + buffer1_addr, buffer2_addr); +} + +void SetAppCpuTimeLimit(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 value = cmd_buff[1]; + u32 percent = cmd_buff[2]; + + if (value != 1) { + LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); + } + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_APT, "(STUBBED) called percent=0x%08X, value=0x%08x", percent, value); +} + +void GetAppCpuTimeLimit(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 value = cmd_buff[1]; + + if (value != 1) { + LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); + } + + // TODO(purpasmart96): This is incorrect, I'm pretty sure the percentage should + // be set by the application. + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0x80; // Set to 80% + + LOG_WARNING(Service_APT, "(STUBBED) called value=0x%08x", value); +} + +void APTInit() { + AddService(new APT_A_Interface); + AddService(new APT_S_Interface); + AddService(new APT_U_Interface); + + // Load the shared system font (if available). + // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header + // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided + // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file + // "shared_font.bin" in the Citra "sysdata" directory. + + shared_font.clear(); + std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; + + FileUtil::CreateFullPath(filepath); // Create path if not already created + FileUtil::IOFile file(filepath, "rb"); + + if (file.IsOpen()) { + // Read shared font data + shared_font.resize((size_t)file.GetSize()); + file.ReadBytes(shared_font.data(), (size_t)file.GetSize()); + + // Create shared font memory object + shared_font_mem = Kernel::SharedMemory::Create("APT_U:shared_font_mem"); + } else { + LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); + shared_font_mem = nullptr; + } + + lock = Kernel::Mutex::Create(false, "APT_U:Lock"); + + // TODO(bunnei): Check if these are created in Initialize or on APT process startup. + notification_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Notification"); + pause_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Pause"); +} + +void APTShutdown() { + +} + +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h new file mode 100644 index 000000000..a39adbff9 --- /dev/null +++ b/src/core/hle/service/apt/apt.h @@ -0,0 +1,222 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "core/hle/result.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace APT { + +/// Signals used by APT functions +enum class SignalType : u32 { + None = 0x0, + AppJustStarted = 0x1, + ReturningToApp = 0xB, + ExitingApp = 0xC, +}; + +/// App Id's used by APT functions +enum class AppID : u32 { + HomeMenu = 0x101, + AlternateMenu = 0x103, + Camera = 0x110, + FriendsList = 0x112, + GameNotes = 0x113, + InternetBrowser = 0x114, + InstructionManual = 0x115, + Notifications = 0x116, + Miiverse = 0x117, + SoftwareKeyboard1 = 0x201, + Ed = 0x202, + PnoteApp = 0x204, + SnoteApp = 0x205, + Error = 0x206, + Mint = 0x207, + Extrapad = 0x208, + Memolib = 0x209, + Application = 0x300, + SoftwareKeyboard2 = 0x401, +}; + +/** + * APT::Initialize service function + * Service function that initializes the APT process for the running application + * Outputs: + * 1 : Result of the function, 0 on success, otherwise error code + * 3 : Handle to the notification event + * 4 : Handle to the pause event + */ +void Initialize(Service::Interface* self); + +/** + * APT::GetSharedFont service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Virtual address of where shared font will be loaded in memory + * 4 : Handle to shared font memory + */ +void GetSharedFont(Service::Interface* self); + +/** + * APT::NotifyToWait service function + * Inputs: + * 1 : AppID + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void NotifyToWait(Service::Interface* self); + +void GetLockHandle(Service::Interface* self); + +void Enable(Service::Interface* self); + +/** + * APT::GetAppletManInfo service function. + * Inputs: + * 1 : Unknown + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Unknown u32 value + * 3 : Unknown u8 value + * 4 : Home Menu AppId + * 5 : AppID of currently active app + */ +void GetAppletManInfo(Service::Interface* self); + +/** + * APT::IsRegistered service function. This returns whether the specified AppID is registered with NS yet. + * An AppID is "registered" once the process associated with the AppID uses APT:Enable. Home Menu uses this + * command to determine when the launched process is running and to determine when to stop using GSP etc, + * while displaying the "Nintendo 3DS" loading screen. + * Inputs: + * 1 : AppID + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output, 0 = not registered, 1 = registered. + */ +void IsRegistered(Service::Interface* self); + +void InquireNotification(Service::Interface* self); + +/** + * APT::SendParameter service function. This sets the parameter data state. + * Inputs: + * 1 : Source AppID + * 2 : Destination AppID + * 3 : Signal type + * 4 : Parameter buffer size, max size is 0x1000 (this can be zero) + * 5 : Value + * 6 : Handle to the destination process, likely used for shared memory (this can be zero) + * 7 : (Size<<14) | 2 + * 8 : Input parameter buffer ptr + * Outputs: + * 0 : Return Header + * 1 : Result of function, 0 on success, otherwise error code +*/ +void SendParameter(Service::Interface* self); + +/** + * APT::ReceiveParameter service function. This returns the current parameter data from NS state, + * from the source process which set the parameters. Once finished, NS will clear a flag in the NS + * state so that this command will return an error if this command is used again if parameters were + * not set again. This is called when the second Initialize event is triggered. It returns a signal + * type indicating why it was triggered. + * Inputs: + * 1 : AppID + * 2 : Parameter buffer size, max size is 0x1000 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : AppID of the process which sent these parameters + * 3 : Signal type + * 4 : Actual parameter buffer size, this is <= to the the input size + * 5 : Value + * 6 : Handle from the source process which set the parameters, likely used for shared memory + * 7 : Size + * 8 : Output parameter buffer ptr + */ +void ReceiveParameter(Service::Interface* self); + +/** + * APT::GlanceParameter service function. This is exactly the same as APT_U::ReceiveParameter + * (except for the word value prior to the output handle), except this will not clear the flag + * (except when responseword[3]==8 || responseword[3]==9) in NS state. + * Inputs: + * 1 : AppID + * 2 : Parameter buffer size, max size is 0x1000 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Unknown, for now assume AppID of the process which sent these parameters + * 3 : Unknown, for now assume Signal type + * 4 : Actual parameter buffer size, this is <= to the the input size + * 5 : Value + * 6 : Handle from the source process which set the parameters, likely used for shared memory + * 7 : Size + * 8 : Output parameter buffer ptr + */ +void GlanceParameter(Service::Interface* self); + +/** + * APT::CancelParameter service function. When the parameter data is available, and when the above + * specified fields match the ones in NS state(for the ones where the checks are enabled), this + * clears the flag which indicates that parameter data is available + * (same flag cleared by APT:ReceiveParameter). + * Inputs: + * 1 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. + * 2 : Unknown, this is the same as the first unknown field returned by APT:ReceiveParameter. + * 3 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. + * 4 : AppID + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Status flag, 0 = failure due to no parameter data being available, or the above enabled + * fields don't match the fields in NS state. 1 = success. + */ +void CancelParameter(Service::Interface* self); + +/** + * APT::AppletUtility service function + * Inputs: + * 1 : Unknown, but clearly used for something + * 2 : Buffer 1 size (purpose is unknown) + * 3 : Buffer 2 size (purpose is unknown) + * 5 : Buffer 1 address (purpose is unknown) + * 65 : Buffer 2 address (purpose is unknown) + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void AppletUtility(Service::Interface* self); + +/** + * APT::SetAppCpuTimeLimit service function + * Inputs: + * 1 : Value, must be one + * 2 : Percentage of CPU time from 5 to 80 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void SetAppCpuTimeLimit(Service::Interface* self); + +/** + * APT::GetAppCpuTimeLimit service function + * Inputs: + * 1 : Value, must be one + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : System core CPU time percentage + */ +void GetAppCpuTimeLimit(Service::Interface* self); + +/// Initialize the APT service +void APTInit(); + +/// Shutdown the APT service +void APTShutdown(); + +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp new file mode 100644 index 000000000..dbe5c1d87 --- /dev/null +++ b/src/core/hle/service/apt/apt_a.cpp @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/hle.h" +#include "core/hle/service/apt/apt.h" +#include "core/hle/service/apt/apt_a.h" + +namespace Service { +namespace APT { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010040, GetLockHandle, "GetLockHandle?"}, + {0x00020080, Initialize, "Initialize?"}, + {0x00030040, nullptr, "Enable?"}, + {0x00040040, nullptr, "Finalize?"}, + {0x00050040, nullptr, "GetAppletManInfo?"}, + {0x00060040, nullptr, "GetAppletInfo?"}, + {0x000D0080, ReceiveParameter, "ReceiveParameter?"}, + {0x000E0080, GlanceParameter, "GlanceParameter?"}, + {0x003B0040, nullptr, "CancelLibraryApplet?"}, + {0x00430040, nullptr, "NotifyToWait?"}, + {0x00440000, GetSharedFont, "GetSharedFont?"}, + {0x004B00C2, nullptr, "AppletUtility?"}, + {0x00550040, nullptr, "WriteInputToNsState?"}, +}; + +APT_A_Interface::APT_A_Interface() { + Register(FunctionTable); +} + +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/apt_a.h b/src/core/hle/service/apt/apt_a.h index 6cbf1288f..331fb5586 100644 --- a/src/core/hle/service/apt_a.h +++ b/src/core/hle/service/apt/apt_a.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_A +namespace Service { +namespace APT { -namespace APT_A { - -class Interface : public Service::Interface { +class APT_A_Interface : public Service::Interface { public: - Interface(); + APT_A_Interface(); std::string GetPortName() const override { return "APT:A"; } }; -} // namespace +} // namespace APT +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp new file mode 100644 index 000000000..3fd348651 --- /dev/null +++ b/src/core/hle/service/apt/apt_s.cpp @@ -0,0 +1,104 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + + +#include "common/common.h" +#include "common/file_util.h" + +#include "core/hle/hle.h" +#include "core/hle/service/apt/apt.h" +#include "core/hle/service/apt/apt_s.h" + +namespace Service { +namespace APT { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010040, GetLockHandle, "GetLockHandle"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030040, Enable, "Enable"}, + {0x00040040, nullptr, "Finalize"}, + {0x00050040, nullptr, "GetAppletManInfo"}, + {0x00060040, nullptr, "GetAppletInfo"}, + {0x00070000, nullptr, "GetLastSignaledAppletId"}, + {0x00080000, nullptr, "CountRegisteredApplet"}, + {0x00090040, nullptr, "IsRegistered"}, + {0x000A0040, nullptr, "GetAttribute"}, + {0x000B0040, InquireNotification, "InquireNotification"}, + {0x000C0104, nullptr, "SendParameter"}, + {0x000D0080, ReceiveParameter, "ReceiveParameter"}, + {0x000E0080, GlanceParameter, "GlanceParameter"}, + {0x000F0100, nullptr, "CancelParameter"}, + {0x001000C2, nullptr, "DebugFunc"}, + {0x001100C0, nullptr, "MapProgramIdForDebug"}, + {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, + {0x00130000, nullptr, "GetPreparationState"}, + {0x00140040, nullptr, "SetPreparationState"}, + {0x00150140, nullptr, "PrepareToStartApplication"}, + {0x00160040, nullptr, "PreloadLibraryApplet"}, + {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, + {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, + {0x00190040, nullptr, "PrepareToStartSystemApplet"}, + {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, + {0x001B00C4, nullptr, "StartApplication"}, + {0x001C0000, nullptr, "WakeupApplication"}, + {0x001D0000, nullptr, "CancelApplication"}, + {0x001E0084, nullptr, "StartLibraryApplet"}, + {0x001F0084, nullptr, "StartSystemApplet"}, + {0x00200044, nullptr, "StartNewestHomeMenu"}, + {0x00210000, nullptr, "OrderToCloseApplication"}, + {0x00220040, nullptr, "PrepareToCloseApplication"}, + {0x00230040, nullptr, "PrepareToJumpToApplication"}, + {0x00240044, nullptr, "JumpToApplication"}, + {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, + {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, + {0x00270044, nullptr, "CloseApplication"}, + {0x00280044, nullptr, "CloseLibraryApplet"}, + {0x00290044, nullptr, "CloseSystemApplet"}, + {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, + {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, + {0x002C0044, nullptr, "JumpToHomeMenu"}, + {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, + {0x002E0044, nullptr, "LeaveHomeMenu"}, + {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, + {0x00300044, nullptr, "LeaveResidentApplet"}, + {0x00310100, nullptr, "PrepareToDoApplicationJump"}, + {0x00320084, nullptr, "DoApplicationJump"}, + {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, + {0x00340084, nullptr, "SendDeliverArg"}, + {0x00350080, nullptr, "ReceiveDeliverArg"}, + {0x00360040, nullptr, "LoadSysMenuArg"}, + {0x00370042, nullptr, "StoreSysMenuArg"}, + {0x00380040, nullptr, "PreloadResidentApplet"}, + {0x00390040, nullptr, "PrepareToStartResidentApplet"}, + {0x003A0044, nullptr, "StartResidentApplet"}, + {0x003B0040, nullptr, "CancelLibraryApplet"}, + {0x003C0042, nullptr, "SendDspSleep"}, + {0x003D0042, nullptr, "SendDspWakeUp"}, + {0x003E0080, nullptr, "ReplySleepQuery"}, + {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, + {0x00400042, nullptr, "SendCaptureBufferInfo"}, + {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, + {0x00420080, nullptr, "SleepSystem"}, + {0x00430040, NotifyToWait, "NotifyToWait"}, + {0x00440000, GetSharedFont, "GetSharedFont"}, + {0x00450040, nullptr, "GetWirelessRebootInfo"}, + {0x00460104, nullptr, "Wrap"}, + {0x00470104, nullptr, "Unwrap"}, + {0x00480100, nullptr, "GetProgramInfo"}, + {0x00490180, nullptr, "Reboot"}, + {0x004A0040, nullptr, "GetCaptureInfo"}, + {0x004B00C2, AppletUtility, "AppletUtility"}, + {0x004C0000, nullptr, "SetFatalErrDispMode"}, + {0x004D0080, nullptr, "GetAppletProgramInfo"}, + {0x004E0000, nullptr, "HardwareResetAsync"}, + {0x004F0080, nullptr, "SetApplicationCpuTimeLimit"}, + {0x00500040, nullptr, "GetApplicationCpuTimeLimit"}, +}; + +APT_S_Interface::APT_S_Interface() { + Register(FunctionTable); +} + +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/apt_s.h b/src/core/hle/service/apt/apt_s.h index f097c9747..8e87b69af 100644 --- a/src/core/hle/service/apt_s.h +++ b/src/core/hle/service/apt/apt_s.h @@ -6,10 +6,8 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_S - -namespace APT_S { +namespace Service { +namespace APT { // Application and title launching service. These services handle signaling for home/power button as // well. Only one session for either APT service can be open at a time, normally processes close the @@ -18,13 +16,14 @@ namespace APT_S { // svcBreak when the command isn't accessible). See http://3dbrew.org/wiki/NS#APT_Services. /// Interface to "APT:S" service -class Interface : public Service::Interface { +class APT_S_Interface : public Service::Interface { public: - Interface(); + APT_S_Interface(); std::string GetPortName() const override { return "APT:S"; } }; -} // namespace +} // namespace APT +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp new file mode 100644 index 000000000..5ab23801e --- /dev/null +++ b/src/core/hle/service/apt/apt_u.cpp @@ -0,0 +1,103 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + + +#include "common/common.h" +#include "common/file_util.h" + +#include "core/hle/service/apt/apt.h" +#include "core/hle/service/apt/apt_u.h" + +namespace Service { +namespace APT { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010040, GetLockHandle, "GetLockHandle"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030040, Enable, "Enable"}, + {0x00040040, nullptr, "Finalize"}, + {0x00050040, GetAppletManInfo, "GetAppletManInfo"}, + {0x00060040, nullptr, "GetAppletInfo"}, + {0x00070000, nullptr, "GetLastSignaledAppletId"}, + {0x00080000, nullptr, "CountRegisteredApplet"}, + {0x00090040, IsRegistered, "IsRegistered"}, + {0x000A0040, nullptr, "GetAttribute"}, + {0x000B0040, InquireNotification, "InquireNotification"}, + {0x000C0104, SendParameter, "SendParameter"}, + {0x000D0080, ReceiveParameter, "ReceiveParameter"}, + {0x000E0080, GlanceParameter, "GlanceParameter"}, + {0x000F0100, CancelParameter, "CancelParameter"}, + {0x001000C2, nullptr, "DebugFunc"}, + {0x001100C0, nullptr, "MapProgramIdForDebug"}, + {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, + {0x00130000, nullptr, "GetPreparationState"}, + {0x00140040, nullptr, "SetPreparationState"}, + {0x00150140, nullptr, "PrepareToStartApplication"}, + {0x00160040, nullptr, "PreloadLibraryApplet"}, + {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, + {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, + {0x00190040, nullptr, "PrepareToStartSystemApplet"}, + {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, + {0x001B00C4, nullptr, "StartApplication"}, + {0x001C0000, nullptr, "WakeupApplication"}, + {0x001D0000, nullptr, "CancelApplication"}, + {0x001E0084, nullptr, "StartLibraryApplet"}, + {0x001F0084, nullptr, "StartSystemApplet"}, + {0x00200044, nullptr, "StartNewestHomeMenu"}, + {0x00210000, nullptr, "OrderToCloseApplication"}, + {0x00220040, nullptr, "PrepareToCloseApplication"}, + {0x00230040, nullptr, "PrepareToJumpToApplication"}, + {0x00240044, nullptr, "JumpToApplication"}, + {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, + {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, + {0x00270044, nullptr, "CloseApplication"}, + {0x00280044, nullptr, "CloseLibraryApplet"}, + {0x00290044, nullptr, "CloseSystemApplet"}, + {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, + {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, + {0x002C0044, nullptr, "JumpToHomeMenu"}, + {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, + {0x002E0044, nullptr, "LeaveHomeMenu"}, + {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, + {0x00300044, nullptr, "LeaveResidentApplet"}, + {0x00310100, nullptr, "PrepareToDoApplicationJump"}, + {0x00320084, nullptr, "DoApplicationJump"}, + {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, + {0x00340084, nullptr, "SendDeliverArg"}, + {0x00350080, nullptr, "ReceiveDeliverArg"}, + {0x00360040, nullptr, "LoadSysMenuArg"}, + {0x00370042, nullptr, "StoreSysMenuArg"}, + {0x00380040, nullptr, "PreloadResidentApplet"}, + {0x00390040, nullptr, "PrepareToStartResidentApplet"}, + {0x003A0044, nullptr, "StartResidentApplet"}, + {0x003B0040, nullptr, "CancelLibraryApplet"}, + {0x003C0042, nullptr, "SendDspSleep"}, + {0x003D0042, nullptr, "SendDspWakeUp"}, + {0x003E0080, nullptr, "ReplySleepQuery"}, + {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, + {0x00400042, nullptr, "SendCaptureBufferInfo"}, + {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, + {0x00420080, nullptr, "SleepSystem"}, + {0x00430040, NotifyToWait, "NotifyToWait"}, + {0x00440000, GetSharedFont, "GetSharedFont"}, + {0x00450040, nullptr, "GetWirelessRebootInfo"}, + {0x00460104, nullptr, "Wrap"}, + {0x00470104, nullptr, "Unwrap"}, + {0x00480100, nullptr, "GetProgramInfo"}, + {0x00490180, nullptr, "Reboot"}, + {0x004A0040, nullptr, "GetCaptureInfo"}, + {0x004B00C2, AppletUtility, "AppletUtility"}, + {0x004C0000, nullptr, "SetFatalErrDispMode"}, + {0x004D0080, nullptr, "GetAppletProgramInfo"}, + {0x004E0000, nullptr, "HardwareResetAsync"}, + {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, + {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, +}; + +APT_U_Interface::APT_U_Interface() { + Register(FunctionTable); +} + +} // namespace APT +} // namespace Service diff --git a/src/core/hle/service/apt_u.h b/src/core/hle/service/apt/apt_u.h index aad918cfc..8c7fe0ccb 100644 --- a/src/core/hle/service/apt_u.h +++ b/src/core/hle/service/apt/apt_u.h @@ -6,10 +6,8 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_U - -namespace APT_U { +namespace Service { +namespace APT { // Application and title launching service. These services handle signaling for home/power button as // well. Only one session for either APT service can be open at a time, normally processes close the @@ -18,13 +16,14 @@ namespace APT_U { // svcBreak when the command isn't accessible). See http://3dbrew.org/wiki/NS#APT_Services. /// Interface to "APT:U" service -class Interface : public Service::Interface { +class APT_U_Interface : public Service::Interface { public: - Interface(); + APT_U_Interface(); std::string GetPortName() const override { return "APT:U"; } }; -} // namespace +} // namespace APT +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/apt_a.cpp b/src/core/hle/service/apt_a.cpp deleted file mode 100644 index 1c1d92572..000000000 --- a/src/core/hle/service/apt_a.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/hle.h" -#include "core/hle/service/apt_a.h" - -namespace APT_U { - extern void Initialize(Service::Interface* self); - extern void GetLockHandle(Service::Interface* self); - extern void ReceiveParameter(Service::Interface* self); - extern void GlanceParameter(Service::Interface* self); - extern void GetSharedFont(Service::Interface* self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_A - -namespace APT_A { - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, APT_U::GetLockHandle, "GetLockHandle?"}, - {0x00020080, APT_U::Initialize, "Initialize?"}, - {0x00030040, nullptr, "Enable?"}, - {0x00040040, nullptr, "Finalize?"}, - {0x00050040, nullptr, "GetAppletManInfo?"}, - {0x00060040, nullptr, "GetAppletInfo?"}, - {0x000D0080, APT_U::ReceiveParameter, "ReceiveParameter?"}, - {0x000E0080, APT_U::GlanceParameter, "GlanceParameter?"}, - {0x003B0040, nullptr, "CancelLibraryApplet?"}, - {0x00430040, nullptr, "NotifyToWait?"}, - {0x00440000, APT_U::GetSharedFont, "GetSharedFont?"}, - {0x004B00C2, nullptr, "AppletUtility?"}, - {0x00550040, nullptr, "WriteInputToNsState?"}, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { - Register(FunctionTable); -} - -} // namespace diff --git a/src/core/hle/service/apt_s.cpp b/src/core/hle/service/apt_s.cpp deleted file mode 100644 index 686335428..000000000 --- a/src/core/hle/service/apt_s.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - - -#include "common/common.h" -#include "common/file_util.h" - -#include "core/hle/hle.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/mutex.h" -#include "core/hle/kernel/shared_memory.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/service/apt_s.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_S - -namespace APT_U { - extern void GetLockHandle(Service::Interface* self); - extern void Initialize(Service::Interface* self); - extern void Enable(Service::Interface* self); - extern void InquireNotification(Service::Interface* self); - extern void NotifyToWait(Service::Interface* self); - extern void GetSharedFont(Service::Interface* self); - extern void AppletUtility(Service::Interface* self); - extern void GlanceParameter(Service::Interface* self); - extern void ReceiveParameter(Service::Interface* self); -} - -namespace APT_S { - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, APT_U::GetLockHandle, "GetLockHandle"}, - {0x00020080, APT_U::Initialize, "Initialize"}, - {0x00030040, APT_U::Enable, "Enable"}, - {0x00040040, nullptr, "Finalize"}, - {0x00050040, nullptr, "GetAppletManInfo"}, - {0x00060040, nullptr, "GetAppletInfo"}, - {0x00070000, nullptr, "GetLastSignaledAppletId"}, - {0x00080000, nullptr, "CountRegisteredApplet"}, - {0x00090040, nullptr, "IsRegistered"}, - {0x000A0040, nullptr, "GetAttribute"}, - {0x000B0040, APT_U::InquireNotification, "InquireNotification"}, - {0x000C0104, nullptr, "SendParameter"}, - {0x000D0080, APT_U::ReceiveParameter, "ReceiveParameter"}, - {0x000E0080, APT_U::GlanceParameter, "GlanceParameter"}, - {0x000F0100, nullptr, "CancelParameter"}, - {0x001000C2, nullptr, "DebugFunc"}, - {0x001100C0, nullptr, "MapProgramIdForDebug"}, - {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, - {0x00130000, nullptr, "GetPreparationState"}, - {0x00140040, nullptr, "SetPreparationState"}, - {0x00150140, nullptr, "PrepareToStartApplication"}, - {0x00160040, nullptr, "PreloadLibraryApplet"}, - {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, - {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, - {0x00190040, nullptr, "PrepareToStartSystemApplet"}, - {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, - {0x001B00C4, nullptr, "StartApplication"}, - {0x001C0000, nullptr, "WakeupApplication"}, - {0x001D0000, nullptr, "CancelApplication"}, - {0x001E0084, nullptr, "StartLibraryApplet"}, - {0x001F0084, nullptr, "StartSystemApplet"}, - {0x00200044, nullptr, "StartNewestHomeMenu"}, - {0x00210000, nullptr, "OrderToCloseApplication"}, - {0x00220040, nullptr, "PrepareToCloseApplication"}, - {0x00230040, nullptr, "PrepareToJumpToApplication"}, - {0x00240044, nullptr, "JumpToApplication"}, - {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, - {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, - {0x00270044, nullptr, "CloseApplication"}, - {0x00280044, nullptr, "CloseLibraryApplet"}, - {0x00290044, nullptr, "CloseSystemApplet"}, - {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, - {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, - {0x002C0044, nullptr, "JumpToHomeMenu"}, - {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, - {0x002E0044, nullptr, "LeaveHomeMenu"}, - {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, - {0x00300044, nullptr, "LeaveResidentApplet"}, - {0x00310100, nullptr, "PrepareToDoApplicationJump"}, - {0x00320084, nullptr, "DoApplicationJump"}, - {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, - {0x00340084, nullptr, "SendDeliverArg"}, - {0x00350080, nullptr, "ReceiveDeliverArg"}, - {0x00360040, nullptr, "LoadSysMenuArg"}, - {0x00370042, nullptr, "StoreSysMenuArg"}, - {0x00380040, nullptr, "PreloadResidentApplet"}, - {0x00390040, nullptr, "PrepareToStartResidentApplet"}, - {0x003A0044, nullptr, "StartResidentApplet"}, - {0x003B0040, nullptr, "CancelLibraryApplet"}, - {0x003C0042, nullptr, "SendDspSleep"}, - {0x003D0042, nullptr, "SendDspWakeUp"}, - {0x003E0080, nullptr, "ReplySleepQuery"}, - {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, - {0x00400042, nullptr, "SendCaptureBufferInfo"}, - {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, - {0x00420080, nullptr, "SleepSystem"}, - {0x00430040, APT_U::NotifyToWait, "NotifyToWait"}, - {0x00440000, APT_U::GetSharedFont, "GetSharedFont"}, - {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, - {0x00480100, nullptr, "GetProgramInfo"}, - {0x00490180, nullptr, "Reboot"}, - {0x004A0040, nullptr, "GetCaptureInfo"}, - {0x004B00C2, APT_U::AppletUtility, "AppletUtility"}, - {0x004C0000, nullptr, "SetFatalErrDispMode"}, - {0x004D0080, nullptr, "GetAppletProgramInfo"}, - {0x004E0000, nullptr, "HardwareResetAsync"}, - {0x004F0080, nullptr, "SetApplicationCpuTimeLimit"}, - {0x00500040, nullptr, "GetApplicationCpuTimeLimit"}, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { - Register(FunctionTable); -} - -} // namespace diff --git a/src/core/hle/service/apt_u.cpp b/src/core/hle/service/apt_u.cpp deleted file mode 100644 index 2d605a767..000000000 --- a/src/core/hle/service/apt_u.cpp +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - - -#include "common/common.h" -#include "common/file_util.h" - -#include "core/hle/hle.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/mutex.h" -#include "core/hle/kernel/shared_memory.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/service/apt_u.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace APT_U - -namespace APT_U { - -// Address used for shared font (as observed on HW) -// TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via -// https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any -// address other than 0x18000000 due to internal pointers in the shared font dump that would need to -// be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then -// correctly mapping it in Citra, however we still do not understand how the mapping is determined. -static const VAddr SHARED_FONT_VADDR = 0x18000000; - -/// Handle to shared memory region designated to for shared system font -static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; - -static Kernel::SharedPtr<Kernel::Mutex> lock; -static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event -static Kernel::SharedPtr<Kernel::Event> pause_event = 0; ///< APT pause event -static std::vector<u8> shared_font; - -/// Signals used by APT functions -enum class SignalType : u32 { - None = 0x0, - AppJustStarted = 0x1, - ReturningToApp = 0xB, - ExitingApp = 0xC, -}; - -/// App Id's used by APT functions -enum class AppID : u32 { - HomeMenu = 0x101, - AlternateMenu = 0x103, - Camera = 0x110, - FriendsList = 0x112, - GameNotes = 0x113, - InternetBrowser = 0x114, - InstructionManual = 0x115, - Notifications = 0x116, - Miiverse = 0x117, - SoftwareKeyboard1 = 0x201, - Ed = 0x202, - PnoteApp = 0x204, - SnoteApp = 0x205, - Error = 0x206, - Mint = 0x207, - Extrapad = 0x208, - Memolib = 0x209, - Application = 0x300, - SoftwareKeyboard2 = 0x401, -}; - -void Initialize(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(bunnei): Check if these are created in Initialize or on APT process startup. - notification_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Notification"); - pause_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Pause"); - - cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(pause_event).MoveFrom(); - - // TODO(bunnei): Check if these events are cleared/signaled every time Initialize is called. - notification_event->Clear(); - pause_event->Signal(); // Fire start event - - ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); - lock->Release(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error -} - -/** - * APT_U::NotifyToWait service function - * Inputs: - * 1 : AppID - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void NotifyToWait(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. - pause_event->Signal(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); -} - -void GetLockHandle(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - // Not sure what these parameters are used for, but retail apps check that they are 0 after - // GetLockHandle has been called. - cmd_buff[2] = 0; - cmd_buff[3] = 0; - cmd_buff[4] = 0; - - cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); - LOG_TRACE(Service_APT, "called handle=0x%08X", cmd_buff[5]); -} - -void Enable(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk = cmd_buff[1]; // TODO(bunnei): What is this field used for? - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); -} - -/** - * APT_U::GetAppletManInfo service function. - * Inputs: - * 1 : Unknown - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Unknown u32 value - * 3 : Unknown u8 value - * 4 : Home Menu AppId - * 5 : AppID of currently active app - */ -void GetAppletManInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = 0; - cmd_buff[4] = static_cast<u32>(AppID::HomeMenu); // Home menu AppID - cmd_buff[5] = static_cast<u32>(AppID::Application); // TODO(purpasmart96): Do this correctly - - LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); -} - -/** - * APT_U::IsRegistered service function. This returns whether the specified AppID is registered with NS yet. - * An AppID is "registered" once the process associated with the AppID uses APT:Enable. Home Menu uses this - * command to determine when the launched process is running and to determine when to stop using GSP etc, - * while displaying the "Nintendo 3DS" loading screen. - * Inputs: - * 1 : AppID - * Outputs: - * 0 : Return header - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output, 0 = not registered, 1 = registered. - */ -static void IsRegistered(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 1; // Set to registered - LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); -} - -void InquireNotification(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type - LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); -} - -/** - * APT_U::SendParameter service function. This sets the parameter data state. - * Inputs: - * 1 : Source AppID - * 2 : Destination AppID - * 3 : Signal type - * 4 : Parameter buffer size, max size is 0x1000 (this can be zero) - * 5 : Value - * 6 : Handle to the destination process, likely used for shared memory (this can be zero) - * 7 : (Size<<14) | 2 - * 8 : Input parameter buffer ptr - * Outputs: - * 0 : Return Header - * 1 : Result of function, 0 on success, otherwise error code -*/ -static void SendParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 src_app_id = cmd_buff[1]; - u32 dst_app_id = cmd_buff[2]; - u32 signal_type = cmd_buff[3]; - u32 buffer_size = cmd_buff[4]; - u32 value = cmd_buff[5]; - u32 handle = cmd_buff[6]; - u32 size = cmd_buff[7]; - u32 in_param_buffer_ptr = cmd_buff[8]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_APT, "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," - "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", - src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, in_param_buffer_ptr); -} - -/** - * APT_U::ReceiveParameter service function. This returns the current parameter data from NS state, - * from the source process which set the parameters. Once finished, NS will clear a flag in the NS - * state so that this command will return an error if this command is used again if parameters were - * not set again. This is called when the second Initialize event is triggered. It returns a signal - * type indicating why it was triggered. - * Inputs: - * 1 : AppID - * 2 : Parameter buffer size, max size is 0x1000 - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : AppID of the process which sent these parameters - * 3 : Signal type - * 4 : Actual parameter buffer size, this is <= to the the input size - * 5 : Value - * 6 : Handle from the source process which set the parameters, likely used for shared memory - * 7 : Size - * 8 : Output parameter buffer ptr - */ -void ReceiveParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type - cmd_buff[4] = 0x10; // Parameter buffer size (16) - cmd_buff[5] = 0; - cmd_buff[6] = 0; - cmd_buff[7] = 0; - LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); -} - -/** - * APT_U::GlanceParameter service function. This is exactly the same as APT_U::ReceiveParameter - * (except for the word value prior to the output handle), except this will not clear the flag - * (except when responseword[3]==8 || responseword[3]==9) in NS state. - * Inputs: - * 1 : AppID - * 2 : Parameter buffer size, max size is 0x1000 - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Unknown, for now assume AppID of the process which sent these parameters - * 3 : Unknown, for now assume Signal type - * 4 : Actual parameter buffer size, this is <= to the the input size - * 5 : Value - * 6 : Handle from the source process which set the parameters, likely used for shared memory - * 7 : Size - * 8 : Output parameter buffer ptr - */ -void GlanceParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type - cmd_buff[4] = 0x10; // Parameter buffer size (16) - cmd_buff[5] = 0; - cmd_buff[6] = 0; - cmd_buff[7] = 0; - - LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); -} - -/** - * APT_U::CancelParameter service function. When the parameter data is available, and when the above - * specified fields match the ones in NS state(for the ones where the checks are enabled), this - * clears the flag which indicates that parameter data is available - * (same flag cleared by APT:ReceiveParameter). - * Inputs: - * 1 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. - * 2 : Unknown, this is the same as the first unknown field returned by APT:ReceiveParameter. - * 3 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. - * 4 : AppID - * Outputs: - * 0 : Return header - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Status flag, 0 = failure due to no parameter data being available, or the above enabled - * fields don't match the fields in NS state. 1 = success. - */ -static void CancelParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flag1 = cmd_buff[1]; - u32 unk = cmd_buff[2]; - u32 flag2 = cmd_buff[3]; - u32 app_id = cmd_buff[4]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 1; // Set to Success - - LOG_WARNING(Service_APT, "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", - flag1, unk, flag2, app_id); -} - -/** - * APT_U::AppletUtility service function - * Inputs: - * 1 : Unknown, but clearly used for something - * 2 : Buffer 1 size (purpose is unknown) - * 3 : Buffer 2 size (purpose is unknown) - * 5 : Buffer 1 address (purpose is unknown) - * 65 : Buffer 2 address (purpose is unknown) - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void AppletUtility(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // These are from 3dbrew - I'm not really sure what they're used for. - u32 unk = cmd_buff[1]; - u32 buffer1_size = cmd_buff[2]; - u32 buffer2_size = cmd_buff[3]; - u32 buffer1_addr = cmd_buff[5]; - u32 buffer2_addr = cmd_buff[65]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X, buffer1_size=0x%08x, buffer2_size=0x%08x, " - "buffer1_addr=0x%08x, buffer2_addr=0x%08x", unk, buffer1_size, buffer2_size, - buffer1_addr, buffer2_addr); -} - -/** - * APT_U::GetSharedFont service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Virtual address of where shared font will be loaded in memory - * 4 : Handle to shared font memory - */ -void GetSharedFont(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - if (!shared_font.empty()) { - // TODO(bunnei): This function shouldn't copy the shared font every time it's called. - // Instead, it should probably map the shared font as RO memory. We don't currently have - // an easy way to do this, but the copy should be sufficient for now. - memcpy(Memory::GetPointer(SHARED_FONT_VADDR), shared_font.data(), shared_font.size()); - - cmd_buff[0] = 0x00440082; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = SHARED_FONT_VADDR; - cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); - } else { - cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware) - LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT); - } -} - -/** - * APT_U::SetAppCpuTimeLimit service function - * Inputs: - * 1 : Value, must be one - * 2 : Percentage of CPU time from 5 to 80 - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void SetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; - u32 percent = cmd_buff[2]; - - if (value != 1) { - LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); - } - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_APT, "(STUBBED) called percent=0x%08X, value=0x%08x", percent, value); -} - -/** - * APT_U::GetAppCpuTimeLimit service function - * Inputs: - * 1 : Value, must be one - * Outputs: - * 0 : Return header - * 1 : Result of function, 0 on success, otherwise error code - * 2 : System core CPU time percentage - */ -static void GetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; - - if (value != 1) { - LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); - } - - // TODO(purpasmart96): This is incorrect, I'm pretty sure the percentage should - // be set by the application. - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0x80; // Set to 80% - - LOG_WARNING(Service_APT, "(STUBBED) called value=0x%08x", value); -} - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010040, GetLockHandle, "GetLockHandle"}, - {0x00020080, Initialize, "Initialize"}, - {0x00030040, Enable, "Enable"}, - {0x00040040, nullptr, "Finalize"}, - {0x00050040, GetAppletManInfo, "GetAppletManInfo"}, - {0x00060040, nullptr, "GetAppletInfo"}, - {0x00070000, nullptr, "GetLastSignaledAppletId"}, - {0x00080000, nullptr, "CountRegisteredApplet"}, - {0x00090040, IsRegistered, "IsRegistered"}, - {0x000A0040, nullptr, "GetAttribute"}, - {0x000B0040, InquireNotification, "InquireNotification"}, - {0x000C0104, SendParameter, "SendParameter"}, - {0x000D0080, ReceiveParameter, "ReceiveParameter"}, - {0x000E0080, GlanceParameter, "GlanceParameter"}, - {0x000F0100, CancelParameter, "CancelParameter"}, - {0x001000C2, nullptr, "DebugFunc"}, - {0x001100C0, nullptr, "MapProgramIdForDebug"}, - {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, - {0x00130000, nullptr, "GetPreparationState"}, - {0x00140040, nullptr, "SetPreparationState"}, - {0x00150140, nullptr, "PrepareToStartApplication"}, - {0x00160040, nullptr, "PreloadLibraryApplet"}, - {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, - {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, - {0x00190040, nullptr, "PrepareToStartSystemApplet"}, - {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, - {0x001B00C4, nullptr, "StartApplication"}, - {0x001C0000, nullptr, "WakeupApplication"}, - {0x001D0000, nullptr, "CancelApplication"}, - {0x001E0084, nullptr, "StartLibraryApplet"}, - {0x001F0084, nullptr, "StartSystemApplet"}, - {0x00200044, nullptr, "StartNewestHomeMenu"}, - {0x00210000, nullptr, "OrderToCloseApplication"}, - {0x00220040, nullptr, "PrepareToCloseApplication"}, - {0x00230040, nullptr, "PrepareToJumpToApplication"}, - {0x00240044, nullptr, "JumpToApplication"}, - {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, - {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, - {0x00270044, nullptr, "CloseApplication"}, - {0x00280044, nullptr, "CloseLibraryApplet"}, - {0x00290044, nullptr, "CloseSystemApplet"}, - {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, - {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, - {0x002C0044, nullptr, "JumpToHomeMenu"}, - {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, - {0x002E0044, nullptr, "LeaveHomeMenu"}, - {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, - {0x00300044, nullptr, "LeaveResidentApplet"}, - {0x00310100, nullptr, "PrepareToDoApplicationJump"}, - {0x00320084, nullptr, "DoApplicationJump"}, - {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, - {0x00340084, nullptr, "SendDeliverArg"}, - {0x00350080, nullptr, "ReceiveDeliverArg"}, - {0x00360040, nullptr, "LoadSysMenuArg"}, - {0x00370042, nullptr, "StoreSysMenuArg"}, - {0x00380040, nullptr, "PreloadResidentApplet"}, - {0x00390040, nullptr, "PrepareToStartResidentApplet"}, - {0x003A0044, nullptr, "StartResidentApplet"}, - {0x003B0040, nullptr, "CancelLibraryApplet"}, - {0x003C0042, nullptr, "SendDspSleep"}, - {0x003D0042, nullptr, "SendDspWakeUp"}, - {0x003E0080, nullptr, "ReplySleepQuery"}, - {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, - {0x00400042, nullptr, "SendCaptureBufferInfo"}, - {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, - {0x00420080, nullptr, "SleepSystem"}, - {0x00430040, NotifyToWait, "NotifyToWait"}, - {0x00440000, GetSharedFont, "GetSharedFont"}, - {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, - {0x00480100, nullptr, "GetProgramInfo"}, - {0x00490180, nullptr, "Reboot"}, - {0x004A0040, nullptr, "GetCaptureInfo"}, - {0x004B00C2, AppletUtility, "AppletUtility"}, - {0x004C0000, nullptr, "SetFatalErrDispMode"}, - {0x004D0080, nullptr, "GetAppletProgramInfo"}, - {0x004E0000, nullptr, "HardwareResetAsync"}, - {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, - {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { - // Load the shared system font (if available). - // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header - // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided - // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file - // "shared_font.bin" in the Citra "sysdata" directory. - - shared_font.clear(); - std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; - - FileUtil::CreateFullPath(filepath); // Create path if not already created - FileUtil::IOFile file(filepath, "rb"); - - if (file.IsOpen()) { - // Read shared font data - shared_font.resize((size_t)file.GetSize()); - file.ReadBytes(shared_font.data(), (size_t)file.GetSize()); - - // Create shared font memory object - shared_font_mem = Kernel::SharedMemory::Create("APT_U:shared_font_mem"); - } else { - LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); - shared_font_mem = nullptr; - } - - lock = Kernel::Mutex::Create(false, "APT_U:Lock"); - - Register(FunctionTable); -} - -} // namespace diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 71986f7d2..6adadb224 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -3,9 +3,13 @@ // Refer to the license.txt file included. #include <algorithm> -#include "common/make_unique.h" -#include "core/file_sys/archive_systemsavedata.h" + +#include "core/hle/service/fs/archive.h" +#include "core/hle/service/service.h" #include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/cfg/cfg_i.h" +#include "core/hle/service/cfg/cfg_s.h" +#include "core/hle/service/cfg/cfg_u.h" namespace Service { namespace CFG { @@ -36,7 +40,8 @@ const std::array<float, 8> STEREO_CAMERA_SETTINGS = { static const u32 CONFIG_SAVEFILE_SIZE = 0x8000; static std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer; -static std::unique_ptr<FileSys::Archive_SystemSaveData> cfg_system_save_data; +static Service::FS::ArchiveHandle cfg_system_save_data_archive; +static const std::vector<u8> cfg_system_savedata_id = { 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00 }; ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) { // Read the header @@ -102,19 +107,22 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const u8* data ResultCode DeleteConfigNANDSaveFile() { FileSys::Path path("config"); - if (cfg_system_save_data->DeleteFile(path)) - return RESULT_SUCCESS; - return ResultCode(-1); // TODO(Subv): Find the right error code + return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); } ResultCode UpdateConfigNANDSavegame() { FileSys::Mode mode = {}; mode.write_flag = 1; mode.create_flag = 1; + FileSys::Path path("config"); - auto file = cfg_system_save_data->OpenFile(path, mode); - ASSERT_MSG(file != nullptr, "could not open file"); - file->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); + + auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); + ASSERT_MSG(config_result.Succeeded(), "could not open file"); + + auto config = config_result.MoveFrom(); + config->backend->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); + return RESULT_SUCCESS; } @@ -163,27 +171,37 @@ ResultCode FormatConfig() { } void CFGInit() { - // TODO(Subv): In the future we should use the FS service to query this archive, - // currently it is not possible because you can only have one open archive of the same type at any time - std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); - cfg_system_save_data = Common::make_unique<FileSys::Archive_SystemSaveData>( - nand_directory, CFG_SAVE_ID); - if (!cfg_system_save_data->Initialize()) { - LOG_CRITICAL(Service_CFG, "Could not initialize SystemSaveData archive for the CFG:U service"); - return; + AddService(new CFG_I_Interface); + AddService(new CFG_S_Interface); + AddService(new CFG_U_Interface); + + // Open the SystemSaveData archive 0x00010017 + FileSys::Path archive_path(cfg_system_savedata_id); + auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); + + // If the archive didn't exist, create the files inside + if (archive_result.Code().description == ErrorDescription::FS_NotFormatted) { + // Format the archive to create the directories + Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); + + // Open it again to get a valid archive now that the folder exists + archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); } - // TODO(Subv): All this code should be moved to cfg:i, - // it's only here because we do not currently emulate the lower level code that uses that service - // Try to open the file in read-only mode to check its existence - FileSys::Mode mode = {}; - mode.read_flag = 1; - FileSys::Path path("config"); - auto file = cfg_system_save_data->OpenFile(path, mode); + ASSERT_MSG(archive_result.Succeeded(), "Could not open the CFG SystemSaveData archive!"); + + cfg_system_save_data_archive = *archive_result; + + FileSys::Path config_path("config"); + FileSys::Mode open_mode = {}; + open_mode.read_flag = 1; - // Load the config if it already exists - if (file != nullptr) { - file->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); + auto config_result = Service::FS::OpenFileFromArchive(*archive_result, config_path, open_mode); + + // Read the file if it already exists + if (config_result.Succeeded()) { + auto config = config_result.MoveFrom(); + config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); return; } @@ -191,10 +209,12 @@ void CFGInit() { // TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals CONSOLE_USERNAME_BLOCK.ng_word = 0; CONSOLE_USERNAME_BLOCK.zero = 0; + // Copy string to buffer and pad with zeros at the end auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14); std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size, std::end(CONSOLE_USERNAME_BLOCK.username), 0); + FormatConfig(); } diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index 20b09a8cb..6d1eee4e0 100644 --- a/src/core/hle/service/cfg/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp @@ -6,10 +6,8 @@ #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg_i.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_I - -namespace CFG_I { +namespace Service { +namespace CFG { /** * CFG_I::GetConfigInfoBlk8 service function @@ -99,11 +97,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x08180042, nullptr, "SecureInfoGetSerialNo"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +CFG_I_Interface::CFG_I_Interface() { Register(FunctionTable); } -} // namespace +} // namespace CFG +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/cfg/cfg_i.h b/src/core/hle/service/cfg/cfg_i.h index a498dd589..d0a2cce39 100644 --- a/src/core/hle/service/cfg/cfg_i.h +++ b/src/core/hle/service/cfg/cfg_i.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_I +namespace Service { +namespace CFG { -namespace CFG_I { - -class Interface : public Service::Interface { +class CFG_I_Interface : public Service::Interface { public: - Interface(); + CFG_I_Interface(); std::string GetPortName() const override { return "cfg:i"; } }; -} // namespace +} // namespace CFG +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp index d80aeae8d..d9a3e5d51 100644 --- a/src/core/hle/service/cfg/cfg_s.cpp +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -6,10 +6,8 @@ #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg_s.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_S - -namespace CFG_S { +namespace Service { +namespace CFG { /** * CFG_S::GetConfigInfoBlk2 service function @@ -87,11 +85,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x04090000, nullptr, "UpdateConfigBlk00040003"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +CFG_S_Interface::CFG_S_Interface() { Register(FunctionTable); } -} // namespace +} // namespace CFG +} // namespace Service diff --git a/src/core/hle/service/cfg/cfg_s.h b/src/core/hle/service/cfg/cfg_s.h index d8b67137f..5568d6485 100644 --- a/src/core/hle/service/cfg/cfg_s.h +++ b/src/core/hle/service/cfg/cfg_s.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_S +namespace Service { +namespace CFG { -namespace CFG_S { - -class Interface : public Service::Interface { +class CFG_S_Interface : public Service::Interface { public: - Interface(); + CFG_S_Interface(); std::string GetPortName() const override { return "cfg:s"; } }; -} // namespace +} // namespace CFG +} // namespace Service diff --git a/src/core/hle/service/cfg/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp index a65da90c5..c8c1c5b17 100644 --- a/src/core/hle/service/cfg/cfg_u.cpp +++ b/src/core/hle/service/cfg/cfg_u.cpp @@ -10,10 +10,8 @@ #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg_u.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_U - -namespace CFG_U { +namespace Service { +namespace CFG { // TODO(Link Mauve): use a constexpr once MSVC starts supporting it. #define C(code) ((code)[0] | ((code)[1] << 8)) @@ -241,11 +239,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +CFG_U_Interface::CFG_U_Interface() { Register(FunctionTable); } -} // namespace +} // namespace CFG +} // namespace Service diff --git a/src/core/hle/service/cfg/cfg_u.h b/src/core/hle/service/cfg/cfg_u.h index 9ad73f355..5303d8ac6 100644 --- a/src/core/hle/service/cfg/cfg_u.h +++ b/src/core/hle/service/cfg/cfg_u.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace CFG_U +namespace Service { +namespace CFG { -namespace CFG_U { - -class Interface : public Service::Interface { +class CFG_U_Interface : public Service::Interface { public: - Interface(); + CFG_U_Interface(); std::string GetPortName() const override { return "cfg:u"; } }; -} // namespace +} // namespace CFG +} // namespace Service diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp index 8d765acb5..58c5acd1e 100644 --- a/src/core/hle/service/err_f.cpp +++ b/src/core/hle/service/err_f.cpp @@ -10,8 +10,171 @@ namespace ERR_F { +enum { + ErrSpecifier0 = 0, + ErrSpecifier1 = 1, + ErrSpecifier3 = 3, + ErrSpecifier4 = 4, +}; + +// This is used instead of ResultCode from result.h +// because we can't have non-trivial data members in unions. +union RSL { + u32 raw; + + BitField<0, 10, u32> description; + BitField<10, 8, u32> module; + BitField<21, 6, u32> summary; + BitField<27, 5, u32> level; +}; + +union ErrInfo { + u8 specifier; + + struct { + u8 specifier; // 0x0 + u8 rev_high; // 0x1 + u16 rev_low; // 0x2 + RSL result_code; // 0x4 + u32 address; // 0x8 + INSERT_PADDING_BYTES(4); // 0xC + u32 pid_low; // 0x10 + u32 pid_high; // 0x14 + u32 aid_low; // 0x18 + u32 aid_high; // 0x1C + } errtype1; + + struct { + u8 specifier; // 0x0 + u8 rev_high; // 0x1 + u16 rev_low; // 0x2 + INSERT_PADDING_BYTES(0xC); // 0x4 + u32 pid_low; // 0x10 + u32 pid_high; // 0x14 + u32 aid_low; // 0x18 + u32 aid_high; // 0x1C + u8 error_type; // 0x20 + INSERT_PADDING_BYTES(3); // 0x21 + u32 fault_status_reg; // 0x24 + u32 fault_addr; // 0x28 + u32 fpexc; // 0x2C + u32 finst; // 0x30 + u32 finst2; // 0x34 + INSERT_PADDING_BYTES(0x34); // 0x38 + u32 sp; // 0x6C + u32 pc; // 0x70 + u32 lr; // 0x74 + u32 cpsr; // 0x78 + } errtype3; + + struct { + u8 specifier; // 0x0 + u8 rev_high; // 0x1 + u16 rev_low; // 0x2 + RSL result_code; // 0x4 + INSERT_PADDING_BYTES(8); // 0x8 + u32 pid_low; // 0x10 + u32 pid_high; // 0x14 + u32 aid_low; // 0x18 + u32 aid_high; // 0x1C + char debug_string1[0x2E]; // 0x20 + char debug_string2[0x2E]; // 0x4E + } errtype4; +}; + +enum { + PrefetchAbort = 0, + DataAbort = 1, + UndefInstr = 2, + VectorFP = 3 +}; + +static std::string GetErrInfo3Type(u8 type_code) { + switch (type_code) { + case PrefetchAbort: return "Prefetch Abort"; + case DataAbort: return "Data Abort"; + case UndefInstr: return "Undefined Instruction"; + case VectorFP: return "Vector Floating Point"; + default: return "unknown"; + } +} + +static void ThrowFatalError(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + LOG_CRITICAL(Service_ERR, "Fatal error!"); + const ErrInfo* errinfo = reinterpret_cast<ErrInfo*>(&cmd_buff[1]); + + switch (errinfo->specifier) { + case ErrSpecifier0: + case ErrSpecifier1: + { + const auto& errtype = errinfo->errtype1; + LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high); + LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16)); + LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high); + LOG_CRITICAL(Service_ERR, "ADR: 0x%08X", errtype.address); + + LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw); + LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value()); + LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value()); + LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value()); + LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value()); + break; + } + + case ErrSpecifier3: + { + const auto& errtype = errinfo->errtype3; + LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high); + LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16)); + LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high); + LOG_CRITICAL(Service_ERR, "TYPE: %s", GetErrInfo3Type(errtype.error_type).c_str()); + + LOG_CRITICAL(Service_ERR, "PC: 0x%08X", errtype.pc); + LOG_CRITICAL(Service_ERR, "LR: 0x%08X", errtype.lr); + LOG_CRITICAL(Service_ERR, "SP: 0x%08X", errtype.sp); + LOG_CRITICAL(Service_ERR, "CPSR: 0x%08X", errtype.cpsr); + + switch (errtype.error_type) { + case PrefetchAbort: + case DataAbort: + LOG_CRITICAL(Service_ERR, "Fault Address: 0x%08X", errtype.fault_addr); + LOG_CRITICAL(Service_ERR, "Fault Status Register: 0x%08X", errtype.fault_status_reg); + break; + case VectorFP: + LOG_CRITICAL(Service_ERR, "FPEXC: 0x%08X", errtype.fpexc); + LOG_CRITICAL(Service_ERR, "FINST: 0x%08X", errtype.finst); + LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X", errtype.finst2); + break; + } + break; + } + + case ErrSpecifier4: + { + const auto& errtype = errinfo->errtype4; + LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high); + LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16)); + LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high); + + LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw); + LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value()); + LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value()); + LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value()); + LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value()); + + LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string1); + LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string2); + break; + } + } + + cmd_buff[1] = 0; // No error +} + const Interface::FunctionInfo FunctionTable[] = { - {0x00010800, nullptr, "ThrowFatalError"} + {0x00010800, ThrowFatalError, "ThrowFatalError"} }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index a69c4f25b..9da2e7aa2 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -18,8 +18,11 @@ #include "core/file_sys/archive_savedata.h" #include "core/file_sys/archive_savedatacheck.h" #include "core/file_sys/archive_sdmc.h" +#include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/directory_backend.h" +#include "core/hle/service/service.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/fs/fs_user.h" #include "core/hle/result.h" // Specializes std::hash for ArchiveIdCode, so that we can use it in std::unordered_map. @@ -418,6 +421,8 @@ ResultCode CreateExtSaveData(u32 high, u32 low) { void ArchiveInit() { next_handle = 1; + AddService(new FS::Interface); + // TODO(Subv): Add the other archive types (see here for the known types: // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes). @@ -449,6 +454,9 @@ void ArchiveInit() { // Create the SaveDataCheck archive, basically a small variation of the RomFS archive auto savedatacheck_factory = Common::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); + + auto systemsavedata_factory = Common::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); + RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); } /// Shutdown archives diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 71ee4ff55..eb312496e 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -487,6 +487,15 @@ static void FormatThisUserSaveData(Service::Interface* self) { cmd_buff[1] = FormatArchive(ArchiveIdCode::SaveData).raw; } +/** + * FS_User::CreateExtSaveData service function + * Inputs: + * 0: 0x08510242 + * 1: High word of the saveid to create + * 2: Low word of the saveid to create + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ static void CreateExtSaveData(Service::Interface* self) { // TODO(Subv): Figure out the other parameters. u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -496,7 +505,22 @@ static void CreateExtSaveData(Service::Interface* self) { cmd_buff[1] = CreateExtSaveData(save_high, save_low).raw; } -const FSUserInterface::FunctionInfo FunctionTable[] = { +/** + * FS_User::CardSlotIsInserted service function. + * Inputs: + * 0: 0x08210000 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Whether there is a game card inserted into the slot or not. + */ +static void CardSlotIsInserted(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; + LOG_WARNING(Service_FS, "(STUBBED) called"); +} + +const Interface::FunctionInfo FunctionTable[] = { {0x000100C6, nullptr, "Dummy1"}, {0x040100C4, nullptr, "Control"}, {0x08010002, Initialize, "Initialize"}, @@ -531,7 +555,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = { {0x081E0042, nullptr, "GetNandLog"}, {0x081F0000, nullptr, "ClearSdmcLog"}, {0x08200000, nullptr, "ClearNandLog"}, - {0x08210000, nullptr, "CardSlotIsInserted"}, + {0x08210000, CardSlotIsInserted, "CardSlotIsInserted"}, {0x08220000, nullptr, "CardSlotPowerOn"}, {0x08230000, nullptr, "CardSlotPowerOff"}, {0x08240000, nullptr, "CardSlotGetCardIFPowerStatus"}, @@ -590,7 +614,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = { //////////////////////////////////////////////////////////////////////////////////////////////////// // Interface class -FSUserInterface::FSUserInterface() { +Interface::Interface() { Register(FunctionTable); } diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 2d896dd5f..bb6ab195e 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -6,16 +6,13 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace FS_User - namespace Service { namespace FS { /// Interface to "fs:USER" service -class FSUserInterface : public Service::Interface { +class Interface : public Service::Interface { public: - FSUserInterface(); + Interface(); std::string GetPortName() const override { return "fs:USER"; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 7cb01729e..e0689be2e 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,7 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/service.h" #include "core/hle/service/hid/hid.h" +#include "core/hle/service/hid/hid_spvr.h" +#include "core/hle/service/hid/hid_user.h" #include "core/arm/arm_interface.h" #include "core/hle/kernel/event.h" @@ -35,6 +38,19 @@ static inline PadData* GetPadData() { return reinterpret_cast<PadData*>(g_shared_mem->GetPointer().ValueOr(nullptr)); } +// TODO(peachum): +// Add a method for setting analog input from joystick device for the circle Pad. +// +// This method should: +// * Be called after both PadButton<Press, Release>(). +// * Be called before PadUpdateComplete() +// * Set current PadEntry.circle_pad_<axis> using analog data +// * Set PadData.raw_circle_pad_data +// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_x >= 41 +// * Set PadData.current_state.circle_up = 1 if current PadEntry.circle_pad_y >= 41 +// * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41 +// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41 + /** * Circle Pad from keys. * @@ -121,9 +137,25 @@ void PadUpdateComplete() { g_event_pad_or_touch_2->Signal(); } +void GetIPCHandles(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = 0; // No error + // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) + cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::g_shared_mem).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_1).MoveFrom(); + cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_2).MoveFrom(); + cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::g_event_accelerometer).MoveFrom(); + cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::g_event_gyroscope).MoveFrom(); + cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::g_event_debug_pad).MoveFrom(); +} + void HIDInit() { using namespace Kernel; + AddService(new HID_U_Interface); + AddService(new HID_SPVR_Interface); + g_shared_mem = SharedMemory::Create("HID:SharedMem"); // Create event handles diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 2116d2ca3..9c6e86f77 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -7,6 +7,7 @@ #include <array> #include "core/hle/kernel/kernel.h" +#include "core/hle/service/service.h" #include "common/bit_field.h" namespace Kernel { @@ -47,6 +48,15 @@ struct PadState { BitField<10, 1, u32> x; BitField<11, 1, u32> y; + BitField<14, 1, u32> zl; + BitField<15, 1, u32> zr; + + BitField<20, 1, u32> touch; + + BitField<24, 1, u32> c_right; + BitField<25, 1, u32> c_left; + BitField<26, 1, u32> c_up; + BitField<27, 1, u32> c_down; BitField<28, 1, u32> circle_right; BitField<29, 1, u32> circle_left; BitField<30, 1, u32> circle_up; @@ -99,11 +109,37 @@ const PadState PAD_R = {{1u << 8}}; const PadState PAD_L = {{1u << 9}}; const PadState PAD_X = {{1u << 10}}; const PadState PAD_Y = {{1u << 11}}; + +const PadState PAD_ZL = {{1u << 14}}; +const PadState PAD_ZR = {{1u << 15}}; + +const PadState PAD_TOUCH = {{1u << 20}}; + +const PadState PAD_C_RIGHT = {{1u << 24}}; +const PadState PAD_C_LEFT = {{1u << 25}}; +const PadState PAD_C_UP = {{1u << 26}}; +const PadState PAD_C_DOWN = {{1u << 27}}; const PadState PAD_CIRCLE_RIGHT = {{1u << 28}}; const PadState PAD_CIRCLE_LEFT = {{1u << 29}}; const PadState PAD_CIRCLE_UP = {{1u << 30}}; const PadState PAD_CIRCLE_DOWN = {{1u << 31}}; +/** + * HID::GetIPCHandles service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Unused + * 3 : Handle to HID_User shared memory + * 4 : Event signaled by HID_User + * 5 : Event signaled by HID_User + * 6 : Event signaled by HID_User + * 7 : Gyroscope event + * 8 : Event signaled by HID_User + */ +void GetIPCHandles(Interface* self); + // Methods for updating the HID module's state void PadButtonPress(const PadState& pad_state); void PadButtonRelease(const PadState& pad_state); diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp index 8f06b224d..790dcabbf 100644 --- a/src/core/hle/service/hid/hid_spvr.cpp +++ b/src/core/hle/service/hid/hid_spvr.cpp @@ -3,19 +3,14 @@ // Refer to the license.txt file included. #include "core/hle/hle.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_spvr.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HID_SPVR - -namespace HID_User { - extern void GetIPCHandles(Service::Interface* self); -} - -namespace HID_SPVR { +namespace Service { +namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, HID_User::GetIPCHandles, "GetIPCHandles"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, {0x000B0000, nullptr, "StartAnalogStickCalibration"}, {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, {0x00110000, nullptr, "EnableAccelerometer"}, @@ -27,11 +22,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00170000, nullptr, "GetSoundVolume"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +HID_SPVR_Interface::HID_SPVR_Interface() { Register(FunctionTable); } -} // namespace +} // namespace HID +} // namespace Service diff --git a/src/core/hle/service/hid/hid_spvr.h b/src/core/hle/service/hid/hid_spvr.h index 53ddc8569..ba61583d2 100644 --- a/src/core/hle/service/hid/hid_spvr.h +++ b/src/core/hle/service/hid/hid_spvr.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HID_SPVR +namespace Service { +namespace HID { -namespace HID_SPVR { - -class Interface : public Service::Interface { +class HID_SPVR_Interface : public Service::Interface { public: - Interface(); + HID_SPVR_Interface(); std::string GetPortName() const override { return "hid:SPVR"; } }; -} // namespace +} // namespace HID +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp index 7f464705f..1d0accefe 100644 --- a/src/core/hle/service/hid/hid_user.cpp +++ b/src/core/hle/service/hid/hid_user.cpp @@ -6,54 +6,10 @@ #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/hid.h" -#include "hid_user.h" +#include "core/hle/service/hid/hid_user.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HID_User - -namespace HID_User { - - -// TODO(peachum): -// Add a method for setting analog input from joystick device for the circle Pad. -// -// This method should: -// * Be called after both PadButton<Press, Release>(). -// * Be called before PadUpdateComplete() -// * Set current PadEntry.circle_pad_<axis> using analog data -// * Set PadData.raw_circle_pad_data -// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_x >= 41 -// * Set PadData.current_state.circle_up = 1 if current PadEntry.circle_pad_y >= 41 -// * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41 -// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41 - - -/** - * HID_User::GetIPCHandles service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Unused - * 3 : Handle to HID_User shared memory - * 4 : Event signaled by HID_User - * 5 : Event signaled by HID_User - * 6 : Event signaled by HID_User - * 7 : Gyroscope event - * 8 : Event signaled by HID_User - */ -void GetIPCHandles(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = 0; // No error - // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) - cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::g_shared_mem).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_1).MoveFrom(); - cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_2).MoveFrom(); - cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::g_event_accelerometer).MoveFrom(); - cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::g_event_gyroscope).MoveFrom(); - cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::g_event_debug_pad).MoveFrom(); -} +namespace Service { +namespace HID { const Interface::FunctionInfo FunctionTable[] = { {0x000A0000, GetIPCHandles, "GetIPCHandles"}, @@ -66,11 +22,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00170000, nullptr, "GetSoundVolume"}, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +HID_U_Interface::HID_U_Interface() { Register(FunctionTable); } -} // namespace +} // namespace HID +} // namespace Service diff --git a/src/core/hle/service/hid/hid_user.h b/src/core/hle/service/hid/hid_user.h index 1d9929e67..0eeec2c25 100644 --- a/src/core/hle/service/hid/hid_user.h +++ b/src/core/hle/service/hid/hid_user.h @@ -6,24 +6,23 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HID_User - // This service is used for interfacing to physical user controls. // Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad. -namespace HID_User { - +namespace Service { +namespace HID { + /** * HID service interface. */ -class Interface : public Service::Interface { +class HID_U_Interface : public Service::Interface { public: - Interface(); + HID_U_Interface(); std::string GetPortName() const override { return "hid:USER"; } }; -} // namespace +} // namespace HID +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp new file mode 100644 index 000000000..56c918d4f --- /dev/null +++ b/src/core/hle/service/ptm/ptm.cpp @@ -0,0 +1,76 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/service.h" +#include "core/hle/service/fs/archive.h" +#include "core/hle/service/ptm/ptm.h" +#include "core/hle/service/ptm/ptm_play.h" +#include "core/hle/service/ptm/ptm_sysm.h" +#include "core/hle/service/ptm/ptm_u.h" + +namespace Service { +namespace PTM { + +/// Values for the default gamecoin.dat file +static const GameCoin default_game_coin = { 0x4F00, 42, 0, 0, 0, 2014, 12, 29 }; + +/// Id of the SharedExtData archive used by the PTM process +static const std::vector<u8> ptm_shared_extdata_id = {0, 0, 0, 0, 0x0B, 0, 0, 0xF0, 0, 0, 0, 0}; + +static bool shell_open = true; + +static bool battery_is_charging = true; + +u32 GetAdapterState() { + // TODO(purpasmart96): This function is only a stub, + // it returns a valid result without implementing full functionality. + return battery_is_charging ? 1 : 0; +} + +u32 GetShellState() { + return shell_open ? 1 : 0; +} + +ChargeLevels GetBatteryLevel() { + // TODO(purpasmart96): This function is only a stub, + // it returns a valid result without implementing full functionality. + return ChargeLevels::CompletelyFull; // Set to a completely full battery +} + +void PTMInit() { + AddService(new PTM_Play_Interface); + AddService(new PTM_Sysm_Interface); + AddService(new PTM_U_Interface); + + // Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't exist + FileSys::Path archive_path(ptm_shared_extdata_id); + auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); + // If the archive didn't exist, create the files inside + if (archive_result.Code().description == ErrorDescription::FS_NotFormatted) { + // Format the archive to create the directories + Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); + // Open it again to get a valid archive now that the folder exists + archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); + ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); + + FileSys::Path gamecoin_path("gamecoin.dat"); + FileSys::Mode open_mode = {}; + open_mode.write_flag = 1; + open_mode.create_flag = 1; + // Open the file and write the default gamecoin information + auto gamecoin_result = Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode); + if (gamecoin_result.Succeeded()) { + auto gamecoin = gamecoin_result.MoveFrom(); + gamecoin->backend->Write(0, sizeof(GameCoin), 1, reinterpret_cast<const u8*>(&default_game_coin)); + gamecoin->backend->Close(); + } + } +} + +void PTMShutdown() { + +} + +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h new file mode 100644 index 000000000..f697aae4d --- /dev/null +++ b/src/core/hle/service/ptm/ptm.h @@ -0,0 +1,65 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "core/hle/result.h" + +namespace Service { +namespace PTM { + +/// Charge levels used by PTM functions +enum class ChargeLevels : u32 { + CriticalBattery = 1, + LowBattery = 2, + HalfFull = 3, + MostlyFull = 4, + CompletelyFull = 5, +}; + +/** + * Represents the gamecoin file structure in the SharedExtData archive + * More information in 3dbrew (http://www.3dbrew.org/wiki/Extdata#Shared_Extdata_0xf000000b_gamecoin.dat) + */ +struct GameCoin { + u32 magic; ///< Magic number: 0x4F00 + u16 total_coins; ///< Total Play Coins + u16 total_coins_on_date; ///< Total Play Coins obtained on the date stored below. + u32 step_count; ///< Total step count at the time a new Play Coin was obtained. + u32 last_step_count; ///< Step count for the day the last Play Coin was obtained + u16 year; + u8 month; + u8 day; +}; + +/** + * Returns whether the battery is charging or not. + * It is unknown if GetAdapterState is the same as GetBatteryChargeState, + * it is likely to just be a duplicate function of GetBatteryChargeState + * that controls another part of the HW. + * @returns 1 if the battery is charging, and 0 otherwise. + */ +u32 GetAdapterState(); + +/** + * Returns whether the 3DS's physical shell casing is open or closed + * @returns 1 if the shell is open, and 0 if otherwise + */ +u32 GetShellState(); + +/** + * Get the current battery's charge level. + * @returns The battery's charge level. + */ +ChargeLevels GetBatteryLevel(); + +/// Initialize the PTM service +void PTMInit(); + +/// Shutdown the PTM service +void PTMShutdown(); + +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm_play.cpp b/src/core/hle/service/ptm/ptm_play.cpp index f21d9088e..8e8ae8558 100644 --- a/src/core/hle/service/ptm_play.cpp +++ b/src/core/hle/service/ptm/ptm_play.cpp @@ -3,12 +3,10 @@ // Refer to the license.txt file included. #include "core/hle/hle.h" -#include "core/hle/service/ptm_play.h" +#include "core/hle/service/ptm/ptm_play.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_PLAY - -namespace PTM_PLAY { +namespace Service { +namespace PTM { const Interface::FunctionInfo FunctionTable[] = { { 0x08070082, nullptr, "GetPlayHistory" }, @@ -17,11 +15,9 @@ const Interface::FunctionInfo FunctionTable[] = { { 0x080B0080, nullptr, "CalcPlayHistoryStart" }, }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +PTM_Play_Interface::PTM_Play_Interface() { Register(FunctionTable); } -} // namespace +} // namespace PTM +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/ptm_play.h b/src/core/hle/service/ptm/ptm_play.h index 2f4f0d6fd..e5c3e04df 100644 --- a/src/core/hle/service/ptm_play.h +++ b/src/core/hle/service/ptm/ptm_play.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_PLAY +namespace Service { +namespace PTM { -namespace PTM_PLAY { - -class Interface : public Service::Interface { +class PTM_Play_Interface : public Service::Interface { public: - Interface(); + PTM_Play_Interface(); std::string GetPortName() const override { return "ptm:play"; } }; -} // namespace +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm_sysm.cpp b/src/core/hle/service/ptm/ptm_sysm.cpp index 96ef2dce0..2d841f69c 100644 --- a/src/core/hle/service/ptm_sysm.cpp +++ b/src/core/hle/service/ptm/ptm_sysm.cpp @@ -5,12 +5,22 @@ #include "common/make_unique.h" #include "core/file_sys/archive_extsavedata.h" #include "core/hle/hle.h" -#include "core/hle/service/ptm_sysm.h" +#include "core/hle/service/ptm/ptm_sysm.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_SYSM +namespace Service { +namespace PTM { -namespace PTM_SYSM { +/** + * Returns whether the system is powering off (?) + * Outputs: + * 1: Result code, 0 on success, otherwise error code + * 2: Whether the system is going through a power off + */ +void IsLegacyPowerOff(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = 0; +} const Interface::FunctionInfo FunctionTable[] = { {0x040100C0, nullptr, "SetRtcAlarmEx"}, @@ -37,7 +47,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x080C0080, nullptr, "SetUserTime"}, {0x080D0000, nullptr, "InvalidateSystemTime"}, {0x080E0140, nullptr, "NotifyPlayEvent"}, - {0x080F0000, nullptr, "IsLegacyPowerOff"}, + {0x080F0000, IsLegacyPowerOff, "IsLegacyPowerOff"}, {0x08100000, nullptr, "ClearLegacyPowerOff"}, {0x08110000, nullptr, "GetShellStatus"}, {0x08120000, nullptr, "IsShutdownByBatteryEmpty"}, @@ -45,11 +55,9 @@ const Interface::FunctionInfo FunctionTable[] = { {0x08140000, nullptr, "GetLegacyJumpProhibitedFlag"} }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { +PTM_Sysm_Interface::PTM_Sysm_Interface() { Register(FunctionTable); } -} // namespace +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm_sysm.h b/src/core/hle/service/ptm/ptm_sysm.h index 0f267b214..e37f20546 100644 --- a/src/core/hle/service/ptm_sysm.h +++ b/src/core/hle/service/ptm/ptm_sysm.h @@ -6,18 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_SYSM +namespace Service { +namespace PTM { -namespace PTM_SYSM { - -class Interface : public Service::Interface { +class PTM_Sysm_Interface : public Interface { public: - Interface(); + PTM_Sysm_Interface(); std::string GetPortName() const override { return "ptm:sysm"; } }; -} // namespace +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp new file mode 100644 index 000000000..0af7c8bf6 --- /dev/null +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -0,0 +1,99 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/make_unique.h" + +#include "core/hle/hle.h" +#include "core/hle/service/ptm/ptm.h" +#include "core/hle/service/ptm/ptm_u.h" + +namespace Service { +namespace PTM { + +/** + * PTM_U::GetAdapterState service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, 0 = not charging, 1 = charging. + */ +static void GetAdapterState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = GetAdapterState(); + + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +/* + * PTM_User::GetShellState service function. + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Whether the 3DS's physical shell casing is open (1) or closed (0) + */ +static void GetShellState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = GetShellState(); +} + +/** + * PTM_U::GetBatteryLevel service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Battery level, 5 = completely full battery, 4 = mostly full battery, + * 3 = half full battery, 2 = low battery, 1 = critical battery. + */ +static void GetBatteryLevel(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = static_cast<u32>(GetBatteryLevel()); + + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +/** + * PTM_U::GetBatteryChargeState service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, 0 = not charging, 1 = charging. + */ +static void GetBatteryChargeState(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // TODO(purpasmart96): This function is only a stub, + // it returns a valid result without implementing full functionality. + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = GetAdapterState(); + + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010002, nullptr, "RegisterAlarmClient"}, + {0x00020080, nullptr, "SetRtcAlarm"}, + {0x00030000, nullptr, "GetRtcAlarm"}, + {0x00040000, nullptr, "CancelRtcAlarm"}, + {0x00050000, GetAdapterState, "GetAdapterState"}, + {0x00060000, GetShellState, "GetShellState"}, + {0x00070000, GetBatteryLevel, "GetBatteryLevel"}, + {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, + {0x00090000, nullptr, "GetPedometerState"}, + {0x000A0042, nullptr, "GetStepHistoryEntry"}, + {0x000B00C2, nullptr, "GetStepHistory"}, + {0x000C0000, nullptr, "GetTotalStepCount"}, + {0x000D0040, nullptr, "SetPedometerRecordingMode"}, + {0x000E0000, nullptr, "GetPedometerRecordingMode"}, + {0x000F0084, nullptr, "GetStepHistoryAll"}, +}; + +PTM_U_Interface::PTM_U_Interface() { + Register(FunctionTable); +} + +} // namespace PTM +} // namespace Service diff --git a/src/core/hle/service/ptm_u.h b/src/core/hle/service/ptm/ptm_u.h index a44624fd5..bf132f610 100644 --- a/src/core/hle/service/ptm_u.h +++ b/src/core/hle/service/ptm/ptm_u.h @@ -6,20 +6,17 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_U +namespace Service { +namespace PTM { -// ptm service - -namespace PTM_U { - -class Interface : public Service::Interface { +class PTM_U_Interface : public Interface { public: - Interface(); + PTM_U_Interface(); std::string GetPortName() const override { return "ptm:u"; } }; -} // namespace +} // namespace PTM +} // namespace Service
\ No newline at end of file diff --git a/src/core/hle/service/ptm_u.cpp b/src/core/hle/service/ptm_u.cpp deleted file mode 100644 index 7121d837c..000000000 --- a/src/core/hle/service/ptm_u.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/make_unique.h" - -#include "core/hle/hle.h" -#include "core/hle/service/fs/archive.h" -#include "core/hle/service/ptm_u.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace PTM_U - -namespace PTM_U { - -/** - * Represents the gamecoin file structure in the SharedExtData archive - * More information in 3dbrew (http://www.3dbrew.org/wiki/Extdata#Shared_Extdata_0xf000000b_gamecoin.dat) - */ -struct GameCoin { - u32 magic; ///< Magic number: 0x4F00 - u16 total_coins; ///< Total Play Coins - u16 total_coins_on_date; ///< Total Play Coins obtained on the date stored below. - u32 step_count; ///< Total step count at the time a new Play Coin was obtained. - u32 last_step_count; ///< Step count for the day the last Play Coin was obtained - u16 year; - u8 month; - u8 day; -}; -static const GameCoin default_game_coin = { 0x4F00, 42, 0, 0, 0, 2014, 12, 29 }; -static const std::vector<u8> ptm_shared_extdata_id = {0, 0, 0, 0, 0x0B, 0, 0, 0xF0, 0, 0, 0, 0}; - -/// Charge levels used by PTM functions -enum class ChargeLevels : u32 { - CriticalBattery = 1, - LowBattery = 2, - HalfFull = 3, - MostlyFull = 4, - CompletelyFull = 5, -}; - -static bool shell_open = true; - -static bool battery_is_charging = true; - -/** - * It is unknown if GetAdapterState is the same as GetBatteryChargeState, - * it is likely to just be a duplicate function of GetBatteryChargeState - * that controls another part of the HW. - * PTM_U::GetAdapterState service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output of function, 0 = not charging, 1 = charging. - */ -static void GetAdapterState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = 0; // No error - cmd_buff[2] = battery_is_charging ? 1 : 0; - - LOG_WARNING(Service_PTM, "(STUBBED) called"); -} - -/* - * PTM_User::GetShellState service function. - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Whether the 3DS's physical shell casing is open (1) or closed (0) - */ -static void GetShellState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = 0; - cmd_buff[2] = shell_open ? 1 : 0; -} - -/** - * PTM_U::GetBatteryLevel service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Battery level, 5 = completely full battery, 4 = mostly full battery, - * 3 = half full battery, 2 = low battery, 1 = critical battery. - */ -static void GetBatteryLevel(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = 0; // No error - cmd_buff[2] = static_cast<u32>(ChargeLevels::CompletelyFull); // Set to a completely full battery - - LOG_WARNING(Service_PTM, "(STUBBED) called"); -} - -/** - * PTM_U::GetBatteryChargeState service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output of function, 0 = not charging, 1 = charging. - */ -static void GetBatteryChargeState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = 0; // No error - cmd_buff[2] = battery_is_charging ? 1 : 0; - - LOG_WARNING(Service_PTM, "(STUBBED) called"); -} - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010002, nullptr, "RegisterAlarmClient"}, - {0x00020080, nullptr, "SetRtcAlarm"}, - {0x00030000, nullptr, "GetRtcAlarm"}, - {0x00040000, nullptr, "CancelRtcAlarm"}, - {0x00050000, GetAdapterState, "GetAdapterState"}, - {0x00060000, GetShellState, "GetShellState"}, - {0x00070000, GetBatteryLevel, "GetBatteryLevel"}, - {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, - {0x00090000, nullptr, "GetPedometerState"}, - {0x000A0042, nullptr, "GetStepHistoryEntry"}, - {0x000B00C2, nullptr, "GetStepHistory"}, - {0x000C0000, nullptr, "GetTotalStepCount"}, - {0x000D0040, nullptr, "SetPedometerRecordingMode"}, - {0x000E0000, nullptr, "GetPedometerRecordingMode"}, - {0x000F0084, nullptr, "GetStepHistoryAll"}, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { - Register(FunctionTable); - - // Open the SharedExtSaveData archive 0xF000000B and the gamecoin.dat file - FileSys::Path archive_path(ptm_shared_extdata_id); - auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); - // If the archive didn't exist, create the files inside - if (archive_result.Code().description == ErrorDescription::FS_NotFormatted) { - // Format the archive to create the directories - Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); - // Open it again to get a valid archive now that the folder exists - archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); - ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); - - FileSys::Path gamecoin_path("gamecoin.dat"); - FileSys::Mode open_mode = {}; - open_mode.write_flag = 1; - open_mode.create_flag = 1; - // Open the file and write the default gamecoin information - auto gamecoin_result = Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode); - if (gamecoin_result.Succeeded()) { - auto gamecoin = gamecoin_result.MoveFrom(); - gamecoin->backend->Write(0, sizeof(GameCoin), 1, reinterpret_cast<const u8*>(&default_game_coin)); - gamecoin->backend->Close(); - } - } -} - -} // namespace diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 5dce8068e..91f13cd7e 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -11,26 +11,17 @@ #include "core/hle/service/am_app.h" #include "core/hle/service/am_net.h" #include "core/hle/service/am_sys.h" -#include "core/hle/service/apt_a.h" -#include "core/hle/service/apt_s.h" -#include "core/hle/service/apt_u.h" #include "core/hle/service/boss_p.h" #include "core/hle/service/boss_u.h" #include "core/hle/service/cam_u.h" #include "core/hle/service/cecd_u.h" #include "core/hle/service/cecd_s.h" -#include "core/hle/service/cfg/cfg_i.h" -#include "core/hle/service/cfg/cfg_s.h" -#include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/csnd_snd.h" #include "core/hle/service/dsp_dsp.h" #include "core/hle/service/err_f.h" -#include "core/hle/service/fs/fs_user.h" #include "core/hle/service/frd_a.h" #include "core/hle/service/frd_u.h" #include "core/hle/service/gsp_gpu.h" -#include "core/hle/service/hid/hid_spvr.h" -#include "core/hle/service/hid/hid_user.h" #include "core/hle/service/gsp_lcd.h" #include "core/hle/service/http_c.h" #include "core/hle/service/ir_rst.h" @@ -44,14 +35,17 @@ #include "core/hle/service/ns_s.h" #include "core/hle/service/nwm_uds.h" #include "core/hle/service/pm_app.h" -#include "core/hle/service/ptm_play.h" -#include "core/hle/service/ptm_u.h" -#include "core/hle/service/ptm_sysm.h" #include "core/hle/service/soc_u.h" #include "core/hle/service/srv.h" #include "core/hle/service/ssl_c.h" #include "core/hle/service/y2r_u.h" +#include "core/hle/service/apt/apt.h" +#include "core/hle/service/fs/archive.h" +#include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/ptm/ptm.h" + namespace Service { std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_named_ports; @@ -60,12 +54,12 @@ std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; //////////////////////////////////////////////////////////////////////////////////////////////////// // Module interface -static void AddNamedPort(Interface* interface) { - g_kernel_named_ports.emplace(interface->GetPortName(), interface); +static void AddNamedPort(Interface* interface_) { + g_kernel_named_ports.emplace(interface_->GetPortName(), interface_); } -static void AddService(Interface* interface) { - g_srv_services.emplace(interface->GetPortName(), interface); +void AddService(Interface* interface_) { + g_srv_services.emplace(interface_->GetPortName(), interface_); } /// Initialize ServiceManager @@ -73,31 +67,28 @@ void Init() { AddNamedPort(new SRV::Interface); AddNamedPort(new ERR_F::Interface); + Service::FS::ArchiveInit(); + Service::CFG::CFGInit(); + Service::APT::APTInit(); + Service::PTM::PTMInit(); + Service::HID::HIDInit(); + AddService(new AC_U::Interface); AddService(new ACT_U::Interface); AddService(new AM_APP::Interface); AddService(new AM_NET::Interface); AddService(new AM_SYS::Interface); - AddService(new APT_A::Interface); - AddService(new APT_S::Interface); - AddService(new APT_U::Interface); AddService(new BOSS_P::Interface); AddService(new BOSS_U::Interface); AddService(new CAM_U::Interface); AddService(new CECD_S::Interface); AddService(new CECD_U::Interface); - AddService(new CFG_I::Interface); - AddService(new CFG_S::Interface); - AddService(new CFG_U::Interface); AddService(new CSND_SND::Interface); AddService(new DSP_DSP::Interface); AddService(new FRD_A::Interface); AddService(new FRD_U::Interface); - AddService(new FS::FSUserInterface); AddService(new GSP_GPU::Interface); AddService(new GSP_LCD::Interface); - AddService(new HID_User::Interface); - AddService(new HID_SPVR::Interface); AddService(new HTTP_C::Interface); AddService(new IR_RST::Interface); AddService(new IR_U::Interface); @@ -110,9 +101,6 @@ void Init() { AddService(new NS_S::Interface); AddService(new NWM_UDS::Interface); AddService(new PM_APP::Interface); - AddService(new PTM_PLAY::Interface); - AddService(new PTM_U::Interface); - AddService(new PTM_SYSM::Interface); AddService(new SOC_U::Interface); AddService(new SSL_C::Interface); AddService(new Y2R_U::Interface); @@ -122,6 +110,12 @@ void Init() { /// Shutdown ServiceManager void Shutdown() { + Service::HID::HIDShutdown(); + Service::PTM::PTMShutdown(); + Service::APT::APTShutdown(); + Service::CFG::CFGShutdown(); + Service::FS::ArchiveShutdown(); + g_srv_services.clear(); g_kernel_named_ports.clear(); LOG_DEBUG(Service, "shutdown OK"); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 3370f9f9b..bfe16ebad 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -120,4 +120,7 @@ extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_na /// Map of services registered with the "srv:" service, retrieved using GetServiceHandle. extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; +/// Adds a service to the services table +void AddService(Interface* interface_); + } // namespace diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index a58e04d6d..6607965e1 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -11,6 +11,8 @@ namespace Y2R_U { +static Kernel::SharedPtr<Kernel::Event> completion_event = 0; + /** * Y2R_U::IsBusyConversion service function * Outputs: @@ -26,13 +28,26 @@ static void IsBusyConversion(Service::Interface* self) { LOG_WARNING(Service, "(STUBBED) called"); } +/** + * Y2R_U::GetTransferEndEvent service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 3 : The handle of the completion event + */ +static void GetTransferEndEvent(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010040, nullptr, "SetInputFormat"}, {0x00030040, nullptr, "SetOutputFormat"}, {0x00050040, nullptr, "SetRotation"}, {0x00070040, nullptr, "SetBlockAlignment"}, {0x000D0040, nullptr, "SetTransferEndInterrupt"}, - {0x000F0000, nullptr, "GetTransferEndEvent"}, + {0x000F0000, GetTransferEndEvent, "GetTransferEndEvent"}, {0x00100102, nullptr, "SetSendingY"}, {0x00110102, nullptr, "SetSendingU"}, {0x00120102, nullptr, "SetSendingV"}, @@ -53,6 +68,8 @@ const Interface::FunctionInfo FunctionTable[] = { // Interface class Interface::Interface() { + completion_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "Y2R:Completed"); + Register(FunctionTable); } diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index bd7d92cd1..424ce2ca7 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -18,8 +18,9 @@ #include "core/hw/gpu.h" #include "video_core/command_processor.h" +#include "video_core/utils.h" #include "video_core/video_core.h" - +#include <video_core/color.h> namespace GPU { @@ -112,61 +113,109 @@ inline void Write(u32 addr, const T data) { { const auto& config = g_regs.display_transfer_config; if (config.trigger & 1) { - u8* source_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress())); - u8* dest_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress())); - - // Cheap emulation of horizontal scaling: Just skip each second pixel of the - // input framebuffer. We keep track of this in the pixel_skip variable. - unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1; - - u32 output_width = config.output_width / pixel_skip; - - for (u32 y = 0; y < config.output_height; ++y) { - // TODO: Why does the register seem to hold twice the framebuffer width? + u8* src_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress())); + u8* dst_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress())); + + unsigned horizontal_scale = (config.scale_horizontally != 0) ? 2 : 1; + unsigned vertical_scale = (config.scale_vertically != 0) ? 2 : 1; + + u32 output_width = config.output_width / horizontal_scale; + u32 output_height = config.output_height / vertical_scale; + + if (config.raw_copy) { + // Raw copies do not perform color conversion nor tiled->linear / linear->tiled conversions + // TODO(Subv): Verify if raw copies perform scaling + memcpy(dst_pointer, src_pointer, config.output_width * config.output_height * + GPU::Regs::BytesPerPixel(config.output_format)); + + LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), flags 0x%08X, Raw copy", + config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format), + config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), + config.GetPhysicalOutputAddress(), config.output_width.Value(), config.output_height.Value(), + config.output_format.Value(), config.flags); + + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); + break; + } + // TODO(Subv): Blend the pixels when horizontal / vertical scaling is enabled, + // right now we're just skipping the extra pixels. + for (u32 y = 0; y < output_height; ++y) { for (u32 x = 0; x < output_width; ++x) { - struct { - int r, g, b, a; - } source_color = { 0, 0, 0, 0 }; + Math::Vec4<u8> src_color = { 0, 0, 0, 0 }; + + u32 scaled_x = x * horizontal_scale; + u32 scaled_y = y * vertical_scale; + + u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); + u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); + u32 src_offset; + u32 dst_offset; + + if (config.output_tiled) { + // Interpret the input as linear and the output as tiled + u32 coarse_y = y & ~7; + u32 stride = output_width * dst_bytes_per_pixel; + + src_offset = (scaled_x + scaled_y * config.input_width) * src_bytes_per_pixel; + dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; + } else { + // Interpret the input as tiled and the output as linear + u32 coarse_y = scaled_y & ~7; + u32 stride = config.input_width * src_bytes_per_pixel; + + src_offset = VideoCore::GetMortonOffset(scaled_x, scaled_y, src_bytes_per_pixel) + coarse_y * stride; + dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + } + const u8* src_pixel = src_pointer + src_offset; switch (config.input_format) { case Regs::PixelFormat::RGBA8: - { - // TODO: Most likely got the component order messed up. - u8* srcptr = source_pointer + (x * pixel_skip + y * config.input_width) * 4; - source_color.r = srcptr[0]; // blue - source_color.g = srcptr[1]; // green - source_color.b = srcptr[2]; // red - source_color.a = srcptr[3]; // alpha + src_color = Color::DecodeRGBA8(src_pixel); + break; + + case Regs::PixelFormat::RGB8: + src_color = Color::DecodeRGB8(src_pixel); + break; + + case Regs::PixelFormat::RGB565: + src_color = Color::DecodeRGB565(src_pixel); + break; + + case Regs::PixelFormat::RGB5A1: + src_color = Color::DecodeRGB5A1(src_pixel); + break; + + case Regs::PixelFormat::RGBA4: + src_color = Color::DecodeRGBA4(src_pixel); break; - } default: LOG_ERROR(HW_GPU, "Unknown source framebuffer format %x", config.input_format.Value()); break; } + u8* dst_pixel = dst_pointer + dst_offset; switch (config.output_format) { - /*case Regs::PixelFormat::RGBA8: - { - // TODO: Untested - u8* dstptr = (u32*)(dest_pointer + x * 4 + y * config.output_width * 4); - dstptr[0] = source_color.r; - dstptr[1] = source_color.g; - dstptr[2] = source_color.b; - dstptr[3] = source_color.a; + case Regs::PixelFormat::RGBA8: + Color::EncodeRGBA8(src_color, dst_pixel); break; - }*/ case Regs::PixelFormat::RGB8: - { - // TODO: Most likely got the component order messed up. - u8* dstptr = dest_pointer + (x + y * output_width) * 3; - dstptr[0] = source_color.r; // blue - dstptr[1] = source_color.g; // green - dstptr[2] = source_color.b; // red + Color::EncodeRGB8(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGB565: + Color::EncodeRGB565(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGB5A1: + Color::EncodeRGB5A1(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGBA4: + Color::EncodeRGBA4(src_color, dst_pixel); break; - } default: LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value()); @@ -175,11 +224,11 @@ inline void Write(u32 addr, const T data) { } } - LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x", - config.output_height * output_width * 4, - config.GetPhysicalInputAddress(), (u32)config.input_width, (u32)config.input_height, - config.GetPhysicalOutputAddress(), (u32)output_width, (u32)config.output_height, - config.output_format.Value()); + LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X", + config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format), + config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), + config.GetPhysicalOutputAddress(), output_width, output_height, + config.output_format.Value(), config.flags); GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); } diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index df9aa0d71..737b1e968 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -34,13 +34,6 @@ namespace GPU { // MMIO region 0x1EFxxxxx struct Regs { -// helper macro to properly align structure members. -// Calling INSERT_PADDING_WORDS will add a new member variable with a name like "pad121", -// depending on the current source line to make sure variable names are unique. -#define INSERT_PADDING_WORDS_HELPER1(x, y) x ## y -#define INSERT_PADDING_WORDS_HELPER2(x, y) INSERT_PADDING_WORDS_HELPER1(x, y) -#define INSERT_PADDING_WORDS(num_words) u32 INSERT_PADDING_WORDS_HELPER2(pad, __LINE__)[(num_words)] - // helper macro to make sure the defined structures are of the expected size. #if defined(_MSC_VER) // TODO: MSVC does not support using sizeof() on non-static data members even though this @@ -53,7 +46,7 @@ struct Regs { "Structure size and register block length don't match") #endif - // All of those formats are described in reverse byte order, since the 3DS is little-endian. + // Components are laid out in reverse byte order, most significant bits first. enum class PixelFormat : u32 { RGBA8 = 0, RGB8 = 1, @@ -199,12 +192,13 @@ struct Regs { u32 flags; BitField< 0, 1, u32> flip_data; // flips input data horizontally (TODO) if true + BitField< 1, 1, u32> output_tiled; // Converts from linear to tiled format + BitField< 3, 1, u32> raw_copy; // Copies the data without performing any processing BitField< 8, 3, PixelFormat> input_format; BitField<12, 3, PixelFormat> output_format; - BitField<16, 1, u32> output_tiled; // stores output in a tiled format - // TODO: Not really sure if this actually scales, or even resizes at all. BitField<24, 1, u32> scale_horizontally; + BitField<25, 1, u32> scale_vertically; }; INSERT_PADDING_WORDS(0x1); @@ -238,10 +232,6 @@ struct Regs { INSERT_PADDING_WORDS(0x9c3); -#undef INSERT_PADDING_WORDS_HELPER1 -#undef INSERT_PADDING_WORDS_HELPER2 -#undef INSERT_PADDING_WORDS - static inline size_t NumIds() { return sizeof(Regs) / sizeof(u32); } diff --git a/src/core/mem_map_funcs.cpp b/src/core/mem_map_funcs.cpp index 4f93c0e64..48f61db4e 100644 --- a/src/core/mem_map_funcs.cpp +++ b/src/core/mem_map_funcs.cpp @@ -236,30 +236,12 @@ u8 Read8(const VAddr addr) { u16 Read16(const VAddr addr) { u16_le data = 0; Read<u16_le>(data, addr); - - // Check for 16-bit unaligned memory reads... - if (addr & 1) { - // TODO(bunnei): Implement 16-bit unaligned memory reads - LOG_ERROR(HW_Memory, "16-bit unaligned memory reads are not implemented!"); - } - return (u16)data; } u32 Read32(const VAddr addr) { u32_le data = 0; Read<u32_le>(data, addr); - - // Check for 32-bit unaligned memory reads... - if (addr & 3) { - // ARM allows for unaligned memory reads, however older ARM architectures read out memory - // from unaligned addresses in a shifted way. Our ARM CPU core (SkyEye) corrects for this, - // so therefore expects the memory to be read out in this manner. - // TODO(bunnei): Determine if this is necessary - perhaps it is OK to remove this from both - // SkyEye and here? - int shift = (addr & 3) * 8; - data = (data << shift) | (data >> (32 - shift)); - } return (u32)data; } diff --git a/src/core/settings.h b/src/core/settings.h index e62dd4358..870eea958 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -16,6 +16,8 @@ struct Values { int pad_y_key; int pad_l_key; int pad_r_key; + int pad_zl_key; + int pad_zr_key; int pad_start_key; int pad_select_key; int pad_home_key; @@ -27,6 +29,10 @@ struct Values { int pad_sdown_key; int pad_sleft_key; int pad_sright_key; + int pad_cup_key; + int pad_cdown_key; + int pad_cleft_key; + int pad_cright_key; // Core int gpu_refresh_rate; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 13c3f7b22..4c1e6449a 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -19,6 +19,7 @@ set(HEADERS renderer_opengl/gl_shaders.h renderer_opengl/renderer_opengl.h clipper.h + color.h command_processor.h gpu_debugger.h math.h diff --git a/src/video_core/color.h b/src/video_core/color.h index e86ac1265..35da901f2 100644 --- a/src/video_core/color.h +++ b/src/video_core/color.h @@ -5,28 +5,152 @@ #pragma once #include "common/common_types.h" +#include "video_core/math.h" namespace Color { /// Convert a 1-bit color component to 8 bit -static inline u8 Convert1To8(u8 value) { +inline u8 Convert1To8(u8 value) { return value * 255; } /// Convert a 4-bit color component to 8 bit -static inline u8 Convert4To8(u8 value) { +inline u8 Convert4To8(u8 value) { return (value << 4) | value; } /// Convert a 5-bit color component to 8 bit -static inline u8 Convert5To8(u8 value) { +inline u8 Convert5To8(u8 value) { return (value << 3) | (value >> 2); } /// Convert a 6-bit color component to 8 bit -static inline u8 Convert6To8(u8 value) { +inline u8 Convert6To8(u8 value) { return (value << 2) | (value >> 4); } +/// Convert a 8-bit color component to 1 bit +inline u8 Convert8To1(u8 value) { + return value >> 7; +} + +/// Convert a 8-bit color component to 4 bit +inline u8 Convert8To4(u8 value) { + return value >> 4; +} + +/// Convert a 8-bit color component to 5 bit +inline u8 Convert8To5(u8 value) { + return value >> 3; +} + +/// Convert a 8-bit color component to 6 bit +inline u8 Convert8To6(u8 value) { + return value >> 2; +} + +/** + * Decode a color stored in RGBA8 format + * @param bytes Pointer to encoded source color + * @return Result color decoded as Math::Vec4<u8> + */ +inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) { + return { bytes[3], bytes[2], bytes[1], bytes[0] }; +} + +/** + * Decode a color stored in RGB8 format + * @param bytes Pointer to encoded source color + * @return Result color decoded as Math::Vec4<u8> + */ +inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) { + return { bytes[2], bytes[1], bytes[0], 255 }; +} + +/** + * Decode a color stored in RGB565 format + * @param bytes Pointer to encoded source color + * @return Result color decoded as Math::Vec4<u8> + */ +inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) { + const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); + return { Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F), + Convert5To8(pixel & 0x1F), 255 }; +} + +/** + * Decode a color stored in RGB5A1 format + * @param bytes Pointer to encoded source color + * @return Result color decoded as Math::Vec4<u8> + */ +inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) { + const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); + return { Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F), + Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1) }; +} + +/** + * Decode a color stored in RGBA4 format + * @param bytes Pointer to encoded source color + * @return Result color decoded as Math::Vec4<u8> + */ +inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) { + const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); + return { Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF), + Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF) }; +} + +/** + * Encode a color as RGBA8 format + * @param color Source color to encode + * @param bytes Destination pointer to store encoded color + */ +inline void EncodeRGBA8(const Math::Vec4<u8>& color, u8* bytes) { + bytes[3] = color.r(); + bytes[2] = color.g(); + bytes[1] = color.b(); + bytes[0] = color.a(); +} + +/** + * Encode a color as RGB8 format + * @param color Source color to encode + * @param bytes Destination pointer to store encoded color + */ +inline void EncodeRGB8(const Math::Vec4<u8>& color, u8* bytes) { + bytes[2] = color.r(); + bytes[1] = color.g(); + bytes[0] = color.b(); +} + +/** + * Encode a color as RGB565 format + * @param color Source color to encode + * @param bytes Destination pointer to store encoded color + */ +inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) { + *reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) | + (Convert8To6(color.g()) << 5) | Convert8To5(color.b()); +} + +/** + * Encode a color as RGB5A1 format + * @param color Source color to encode + * @param bytes Destination pointer to store encoded color + */ +inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) { + *reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) | + (Convert8To5(color.g()) << 6) | (Convert8To5(color.b()) << 1) | Convert8To1(color.a()); +} + +/** + * Encode a color as RGBA4 format + * @param color Source color to encode + * @param bytes Destination pointer to store encoded color + */ +inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) { + *reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) | + (Convert8To4(color.g()) << 8) | (Convert8To4(color.b()) << 4) | Convert8To4(color.a()); +} } // namespace diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 586ad62b6..e031871e8 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -4,6 +4,8 @@ #include <boost/range/algorithm/fill.hpp> +#include "common/profiler.h" + #include "clipper.h" #include "command_processor.h" #include "math.h" @@ -25,6 +27,8 @@ static int float_regs_counter = 0; static u32 uniform_write_buffer[4]; +Common::Profiling::TimingCategory category_drawing("Drawing"); + static inline void WritePicaReg(u32 id, u32 value, u32 mask) { if (id >= registers.NumIds()) @@ -53,6 +57,8 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX(trigger_draw): case PICA_REG_INDEX(trigger_draw_indexed): { + Common::Profiling::ScopeTimer scope_timer(category_drawing); + DebugUtils::DumpTevStageConfig(registers.GetTevStages()); if (g_debug_context) diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 0beb72e6b..745c4f4ed 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -23,6 +23,7 @@ #include "video_core/color.h" #include "video_core/math.h" #include "video_core/pica.h" +#include "video_core/utils.h" #include "debug_utils.h" @@ -306,111 +307,69 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() } const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info, bool disable_alpha) { - // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each - // of which is composed of four 2x2 subtiles each of which is composed of four texels. - // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. - // texels are laid out in a 2x2 subtile like this: - // 2 3 - // 0 1 - // - // The full 8x8 tile has the texels arranged like this: - // - // 42 43 46 47 58 59 62 63 - // 40 41 44 45 56 57 60 61 - // 34 35 38 39 50 51 54 55 - // 32 33 36 37 48 49 52 53 - // 10 11 14 15 26 27 30 31 - // 08 09 12 13 24 25 28 29 - // 02 03 06 07 18 19 22 23 - // 00 01 04 05 16 17 20 21 - - const unsigned int block_width = 8; - const unsigned int block_height = 8; - const unsigned int coarse_x = x & ~7; const unsigned int coarse_y = y & ~7; - // Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are - // arranged in a Z-order curve. More details on the bit manipulation at: - // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ - unsigned int i = (x | (y << 8)) & 0x0707; // ---- -210 - i = (i ^ (i << 2)) & 0x1313; // ---2 --10 - i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0 - i = (i | (i >> 7)) & 0x3F; - if (info.format != Regs::TextureFormat::ETC1 && info.format != Regs::TextureFormat::ETC1A4) { // TODO(neobrain): Fix code design to unify vertical block offsets! source += coarse_y * info.stride; } - const unsigned int offset = coarse_x * block_height; - + // TODO: Assert that width/height are multiples of block dimensions switch (info.format) { case Regs::TextureFormat::RGBA8: { - const u8* source_ptr = source + offset * 4 + i * 4; - return { source_ptr[3], source_ptr[2], source_ptr[1], disable_alpha ? (u8)255 : source_ptr[0] }; + auto res = Color::DecodeRGBA8(source + VideoCore::GetMortonOffset(x, y, 4)); + return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; } case Regs::TextureFormat::RGB8: { - const u8* source_ptr = source + offset * 3 + i * 3; - return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; + auto res = Color::DecodeRGB8(source + VideoCore::GetMortonOffset(x, y, 3)); + return { res.r(), res.g(), res.b(), 255 }; } - case Regs::TextureFormat::RGBA5551: + case Regs::TextureFormat::RGB5A1: { - const u16 source_ptr = *(const u16*)(source + offset * 2 + i * 2); - u8 r = (source_ptr >> 11) & 0x1F; - u8 g = ((source_ptr) >> 6) & 0x1F; - u8 b = (source_ptr >> 1) & 0x1F; - u8 a = source_ptr & 1; - return Math::MakeVec<u8>(Color::Convert5To8(r), Color::Convert5To8(g), - Color::Convert5To8(b), disable_alpha ? 255 : Color::Convert1To8(a)); + auto res = Color::DecodeRGB5A1(source + VideoCore::GetMortonOffset(x, y, 2)); + return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; } case Regs::TextureFormat::RGB565: { - const u16 source_ptr = *(const u16*)(source + offset * 2 + i * 2); - u8 r = Color::Convert5To8((source_ptr >> 11) & 0x1F); - u8 g = Color::Convert6To8(((source_ptr) >> 5) & 0x3F); - u8 b = Color::Convert5To8((source_ptr) & 0x1F); - return Math::MakeVec<u8>(r, g, b, 255); + auto res = Color::DecodeRGB565(source + VideoCore::GetMortonOffset(x, y, 2)); + return { res.r(), res.g(), res.b(), 255 }; } case Regs::TextureFormat::RGBA4: { - const u8* source_ptr = source + offset * 2 + i * 2; - u8 r = Color::Convert4To8(source_ptr[1] >> 4); - u8 g = Color::Convert4To8(source_ptr[1] & 0xF); - u8 b = Color::Convert4To8(source_ptr[0] >> 4); - u8 a = Color::Convert4To8(source_ptr[0] & 0xF); - return { r, g, b, disable_alpha ? (u8)255 : a }; + auto res = Color::DecodeRGBA4(source + VideoCore::GetMortonOffset(x, y, 2)); + return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; } case Regs::TextureFormat::IA8: { - const u8* source_ptr = source + offset * 2 + i * 2; + const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 2); if (disable_alpha) { // Show intensity as red, alpha as green return { source_ptr[1], source_ptr[0], 0, 255 }; } else { - return { source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]}; + return { source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0] }; } } case Regs::TextureFormat::I8: { - const u8* source_ptr = source + offset + i; + const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); return { *source_ptr, *source_ptr, *source_ptr, 255 }; } case Regs::TextureFormat::A8: { - const u8* source_ptr = source + offset + i; + const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); if (disable_alpha) { return { *source_ptr, *source_ptr, *source_ptr, 255 }; @@ -421,7 +380,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::IA4: { - const u8* source_ptr = source + offset + i; + const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1); u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); u8 a = Color::Convert4To8((*source_ptr) & 0xF); @@ -436,9 +395,10 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::A4: { - const u8* source_ptr = source + offset / 2 + i / 2; + u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1); + const u8* source_ptr = source + morton_offset / 2; - u8 a = (coarse_x % 2) ? ((*source_ptr)&0xF) : (((*source_ptr) & 0xF0) >> 4); + u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF); a = Color::Convert4To8(a); if (disable_alpha) { @@ -545,7 +505,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture } // Add modifier - unsigned table_index = (x < 2) ? table_index_2.Value() : table_index_1.Value(); + unsigned table_index = (x < 2) ? table_index_1.Value() : table_index_2.Value(); static const auto etc1_modifier_table = std::array<std::array<u8, 2>, 8>{{ { 2, 8 }, { 5, 17 }, { 9, 29 }, { 13, 42 }, diff --git a/src/video_core/pica.h b/src/video_core/pica.h index e4a5ef78e..b14de9278 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -39,13 +39,6 @@ namespace Pica { struct Regs { -// helper macro to properly align structure members. -// Calling INSERT_PADDING_WORDS will add a new member variable with a name like "pad121", -// depending on the current source line to make sure variable names are unique. -#define INSERT_PADDING_WORDS_HELPER1(x, y) x ## y -#define INSERT_PADDING_WORDS_HELPER2(x, y) INSERT_PADDING_WORDS_HELPER1(x, y) -#define INSERT_PADDING_WORDS(num_words) u32 INSERT_PADDING_WORDS_HELPER2(pad, __LINE__)[(num_words)]; - INSERT_PADDING_WORDS(0x10); u32 trigger_irq; @@ -152,7 +145,7 @@ struct Regs { enum class TextureFormat : u32 { RGBA8 = 0, RGB8 = 1, - RGBA5551 = 2, + RGB5A1 = 2, RGB565 = 3, RGBA4 = 4, IA8 = 5, @@ -174,7 +167,7 @@ struct Regs { case TextureFormat::RGB8: return 6; - case TextureFormat::RGBA5551: + case TextureFormat::RGB5A1: case TextureFormat::RGB565: case TextureFormat::RGBA4: case TextureFormat::IA8: @@ -416,10 +409,11 @@ struct Regs { } output_merger; struct { + // Components are laid out in reverse byte order, most significant bits first. enum ColorFormat : u32 { RGBA8 = 0, RGB8 = 1, - RGBA5551 = 2, + RGB5A1 = 2, RGB565 = 3, RGBA4 = 4, }; @@ -709,10 +703,6 @@ struct Regs { INSERT_PADDING_WORDS(0x22); -#undef INSERT_PADDING_WORDS_HELPER1 -#undef INSERT_PADDING_WORDS_HELPER2 -#undef INSERT_PADDING_WORDS - // Map register indices to names readable by humans // Used for debugging purposes, so performance is not an issue here static std::string GetCommandName(int index) { diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index 81df09baf..5861c1926 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -7,12 +7,14 @@ #include "common/common_types.h" #include "common/math_util.h" +#include "core/hw/gpu.h" +#include "debug_utils/debug_utils.h" #include "math.h" +#include "color.h" #include "pica.h" #include "rasterizer.h" #include "vertex_shader.h" - -#include "debug_utils/debug_utils.h" +#include "video_core/utils.h" namespace Pica { @@ -20,59 +22,101 @@ namespace Rasterizer { static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { const PAddr addr = registers.framebuffer.GetColorBufferPhysicalAddress(); - u32* color_buffer = reinterpret_cast<u32*>(Memory::GetPointer(PAddrToVAddr(addr))); // Similarly to textures, the render framebuffer is laid out from bottom to top, too. // NOTE: The framebuffer height register contains the actual FB height minus one. y = (registers.framebuffer.height - y); + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(registers.framebuffer.color_format.Value())); + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * registers.framebuffer.width * bytes_per_pixel; + u8* dst_pixel = Memory::GetPointer(PAddrToVAddr(addr)) + dst_offset; + switch (registers.framebuffer.color_format) { case registers.framebuffer.RGBA8: - { - u32 value = (color.a() << 24) | (color.r() << 16) | (color.g() << 8) | color.b(); - *(color_buffer + x + y * registers.framebuffer.GetWidth()) = value; + Color::EncodeRGBA8(color, dst_pixel); + break; + + case registers.framebuffer.RGB8: + Color::EncodeRGB8(color, dst_pixel); + break; + + case registers.framebuffer.RGB5A1: + Color::EncodeRGB5A1(color, dst_pixel); + break; + + case registers.framebuffer.RGB565: + Color::EncodeRGB565(color, dst_pixel); + break; + + case registers.framebuffer.RGBA4: + Color::EncodeRGBA4(color, dst_pixel); break; - } default: - LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", registers.framebuffer.color_format); + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", registers.framebuffer.color_format.Value()); UNIMPLEMENTED(); } } static const Math::Vec4<u8> GetPixel(int x, int y) { const PAddr addr = registers.framebuffer.GetColorBufferPhysicalAddress(); - u32* color_buffer_u32 = reinterpret_cast<u32*>(Memory::GetPointer(PAddrToVAddr(addr))); y = (registers.framebuffer.height - y); - u32 value = *(color_buffer_u32 + x + y * registers.framebuffer.GetWidth()); - Math::Vec4<u8> ret; - ret.a() = value >> 24; - ret.r() = (value >> 16) & 0xFF; - ret.g() = (value >> 8) & 0xFF; - ret.b() = value & 0xFF; - return ret; + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(registers.framebuffer.color_format.Value())); + u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * registers.framebuffer.width * bytes_per_pixel; + u8* src_pixel = Memory::GetPointer(PAddrToVAddr(addr)) + src_offset; + + switch (registers.framebuffer.color_format) { + case registers.framebuffer.RGBA8: + return Color::DecodeRGBA8(src_pixel); + + case registers.framebuffer.RGB8: + return Color::DecodeRGB8(src_pixel); + + case registers.framebuffer.RGB5A1: + return Color::DecodeRGB5A1(src_pixel); + + case registers.framebuffer.RGB565: + return Color::DecodeRGB565(src_pixel); + + case registers.framebuffer.RGBA4: + return Color::DecodeRGBA4(src_pixel); + + default: + LOG_CRITICAL(Render_Software, "Unknown framebuffer color format %x", registers.framebuffer.color_format.Value()); + UNIMPLEMENTED(); + } + + return {}; } static u32 GetDepth(int x, int y) { const PAddr addr = registers.framebuffer.GetDepthBufferPhysicalAddress(); - u16* depth_buffer = reinterpret_cast<u16*>(Memory::GetPointer(PAddrToVAddr(addr))); + u8* depth_buffer = Memory::GetPointer(PAddrToVAddr(addr)); y = (registers.framebuffer.height - y); + + const u32 coarse_y = y & ~7; + u32 stride = registers.framebuffer.width * 2; // Assuming 16-bit depth buffer format until actual format handling is implemented - return *(depth_buffer + x + y * registers.framebuffer.GetWidth()); + return *(u16*)(depth_buffer + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * stride); } static void SetDepth(int x, int y, u16 value) { const PAddr addr = registers.framebuffer.GetDepthBufferPhysicalAddress(); - u16* depth_buffer = reinterpret_cast<u16*>(Memory::GetPointer(PAddrToVAddr(addr))); + u8* depth_buffer = Memory::GetPointer(PAddrToVAddr(addr)); y = (registers.framebuffer.height - y); + const u32 coarse_y = y & ~7; + u32 stride = registers.framebuffer.width * 2; + // Assuming 16-bit depth buffer format until actual format handling is implemented - *(depth_buffer + x + y * registers.framebuffer.GetWidth()) = value; + *(u16*)(depth_buffer + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * stride) = value; } // NOTE: Assuming that rasterizer coordinates are 12.4 fixed-point values diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 272695174..95ab96340 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -4,7 +4,10 @@ #include "core/hw/gpu.h" #include "core/mem_map.h" + #include "common/emu_window.h" +#include "common/profiler_reporting.h" + #include "video_core/video_core.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -75,9 +78,18 @@ void RendererOpenGL::SwapBuffers() { DrawScreens(); + auto& profiler = Common::Profiling::GetProfilingManager(); + profiler.FinishFrame(); + { + auto aggregator = Common::Profiling::GetTimingResultsAggregator(); + aggregator->AddFrame(profiler.GetPreviousFrameResults()); + } + // Swap buffers render_window->PollEvents(); render_window->SwapBuffers(); + + profiler.BeginFrame(); } /** @@ -242,28 +254,26 @@ void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x * Draws the emulated screens to the emulator window. */ void RendererOpenGL::DrawScreens() { - auto viewport_extent = GetViewportExtent(); - glViewport(viewport_extent.left, viewport_extent.top, viewport_extent.GetWidth(), viewport_extent.GetHeight()); // TODO: Or bottom? + auto layout = render_window->GetFramebufferLayout(); + + glViewport(0, 0, layout.width, layout.height); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program_id); // Set projection matrix - std::array<GLfloat, 3*2> ortho_matrix = MakeOrthographicMatrix((float)resolution_width, (float)resolution_height); + std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width, + (float)layout.height); glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data()); // Bind texture in Texture Unit 0 glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - const float max_width = std::max((float)VideoCore::kScreenTopWidth, (float)VideoCore::kScreenBottomWidth); - const float top_x = 0.5f * (max_width - VideoCore::kScreenTopWidth); - const float bottom_x = 0.5f * (max_width - VideoCore::kScreenBottomWidth); - - DrawSingleScreenRotated(textures[0], top_x, 0, - (float)VideoCore::kScreenTopWidth, (float)VideoCore::kScreenTopHeight); - DrawSingleScreenRotated(textures[1], bottom_x, (float)VideoCore::kScreenTopHeight, - (float)VideoCore::kScreenBottomWidth, (float)VideoCore::kScreenBottomHeight); + DrawSingleScreenRotated(textures[0], (float)layout.top_screen.left, (float)layout.top_screen.top, + (float)layout.top_screen.GetWidth(), (float)layout.top_screen.GetHeight()); + DrawSingleScreenRotated(textures[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, + (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight()); m_current_frame++; } @@ -280,34 +290,6 @@ void RendererOpenGL::SetWindow(EmuWindow* window) { render_window = window; } -MathUtil::Rectangle<unsigned> RendererOpenGL::GetViewportExtent() { - unsigned framebuffer_width; - unsigned framebuffer_height; - std::tie(framebuffer_width, framebuffer_height) = render_window->GetFramebufferSize(); - - float window_aspect_ratio = static_cast<float>(framebuffer_height) / framebuffer_width; - float emulation_aspect_ratio = static_cast<float>(resolution_height) / resolution_width; - - MathUtil::Rectangle<unsigned> viewport_extent; - if (window_aspect_ratio > emulation_aspect_ratio) { - // Window is narrower than the emulation content => apply borders to the top and bottom - unsigned viewport_height = static_cast<unsigned>(std::round(emulation_aspect_ratio * framebuffer_width)); - viewport_extent.left = 0; - viewport_extent.top = (framebuffer_height - viewport_height) / 2; - viewport_extent.right = viewport_extent.left + framebuffer_width; - viewport_extent.bottom = viewport_extent.top + viewport_height; - } else { - // Otherwise, apply borders to the left and right sides of the window. - unsigned viewport_width = static_cast<unsigned>(std::round(framebuffer_height / emulation_aspect_ratio)); - viewport_extent.left = (framebuffer_width - viewport_width) / 2; - viewport_extent.top = 0; - viewport_extent.right = viewport_extent.left + viewport_width; - viewport_extent.bottom = viewport_extent.top + framebuffer_height; - } - - return viewport_extent; -} - /// Initialize the renderer void RendererOpenGL::Init() { render_window->MakeCurrent(); diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 6fd640425..bda793fa5 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -35,4 +35,54 @@ struct TGAHeader { */ void DumpTGA(std::string filename, short width, short height, u8* raw_data); +/** + * Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are + * arranged in a Z-order curve. More details on the bit manipulation at: + * https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ + */ +static inline u32 MortonInterleave(u32 x, u32 y) { + u32 i = (x & 7) | ((y & 7) << 8); // ---- -210 + i = (i ^ (i << 2)) & 0x1313; // ---2 --10 + i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0 + i = (i | (i >> 7)) & 0x3F; + return i; +} + +/** + * Calculates the offset of the position of the pixel in Morton order + */ +static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { + // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each + // of which is composed of four 2x2 subtiles each of which is composed of four texels. + // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. + // texels are laid out in a 2x2 subtile like this: + // 2 3 + // 0 1 + // + // The full 8x8 tile has the texels arranged like this: + // + // 42 43 46 47 58 59 62 63 + // 40 41 44 45 56 57 60 61 + // 34 35 38 39 50 51 54 55 + // 32 33 36 37 48 49 52 53 + // 10 11 14 15 26 27 30 31 + // 08 09 12 13 24 25 28 29 + // 02 03 06 07 18 19 22 23 + // 00 01 04 05 16 17 20 21 + // + // This pattern is what's called Z-order curve, or Morton order. + + const unsigned int block_width = 8; + const unsigned int block_height = 8; + + const unsigned int coarse_x = x & ~7; + const unsigned int coarse_y = y & ~7; + + u32 i = VideoCore::MortonInterleave(x, y); + + const unsigned int offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + } // namespace diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index def868ac7..4eb3e743e 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -17,6 +17,7 @@ #include "vertex_shader.h" #include "debug_utils/debug_utils.h" +using nihstro::OpCode; using nihstro::Instruction; using nihstro::RegisterType; using nihstro::SourceRegister; @@ -90,6 +91,7 @@ struct VertexShaderState { u8 repeat_counter; // How often to repeat until this call stack element is removed u8 loop_increment; // Which value to add to the loop counter after an iteration // TODO: Should this be a signed value? Does it even matter? + u32 loop_address; // The address where we'll return to after each loop iteration }; // TODO: Is there a maximal size for this? @@ -115,6 +117,8 @@ static void ProcessShaderCode(VertexShaderState& state) { if (top.repeat_counter-- == 0) { state.program_counter = &shader_memory[top.return_address]; state.call_stack.pop(); + } else { + state.program_counter = &shader_memory[top.loop_address]; } // TODO: Is "trying again" accurate to hardware? @@ -129,7 +133,7 @@ static void ProcessShaderCode(VertexShaderState& state) { static auto call = [](VertexShaderState& state, u32 offset, u32 num_instructions, u32 return_offset, u8 repeat_count, u8 loop_increment) { state.program_counter = &shader_memory[offset] - 1; // -1 to make sure when incrementing the PC we end up at the correct offset - state.call_stack.push({ offset + num_instructions, return_offset, repeat_count, loop_increment }); + state.call_stack.push({ offset + num_instructions, return_offset, repeat_count, loop_increment, offset }); }; u32 binary_offset = state.program_counter - shader_memory.data(); @@ -151,10 +155,10 @@ static void ProcessShaderCode(VertexShaderState& state) { } }; - switch (instr.opcode.GetInfo().type) { - case Instruction::OpCodeType::Arithmetic: + switch (instr.opcode.Value().GetInfo().type) { + case OpCode::Type::Arithmetic: { - bool is_inverted = 0 != (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::SrcInversed); + bool is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed); // TODO: We don't really support this properly: For instance, the address register // offset needs to be applied to SRC2 instead, etc. // For now, we just abort in this situation. @@ -194,15 +198,15 @@ static void ProcessShaderCode(VertexShaderState& state) { src2[3] = src2[3] * float24::FromFloat32(-1); } - float24* dest = (instr.common.dest < 0x08) ? state.output_register_table[4*instr.common.dest.GetIndex()] - : (instr.common.dest < 0x10) ? dummy_vec4_float24 - : (instr.common.dest < 0x20) ? &state.temporary_registers[instr.common.dest.GetIndex()][0] + float24* dest = (instr.common.dest.Value() < 0x08) ? state.output_register_table[4*instr.common.dest.Value().GetIndex()] + : (instr.common.dest.Value() < 0x10) ? dummy_vec4_float24 + : (instr.common.dest.Value() < 0x20) ? &state.temporary_registers[instr.common.dest.Value().GetIndex()][0] : dummy_vec4_float24; state.debug.max_opdesc_id = std::max<u32>(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); - switch (instr.opcode.EffectiveOpCode()) { - case Instruction::OpCode::ADD: + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::ADD: { for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -214,7 +218,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::MUL: + case OpCode::Id::MUL: { for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -226,7 +230,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::MAX: + case OpCode::Id::MAX: for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -235,11 +239,11 @@ static void ProcessShaderCode(VertexShaderState& state) { } break; - case Instruction::OpCode::DP3: - case Instruction::OpCode::DP4: + case OpCode::Id::DP3: + case OpCode::Id::DP4: { float24 dot = float24::FromFloat32(0.f); - int num_components = (instr.opcode == Instruction::OpCode::DP3) ? 3 : 4; + int num_components = (instr.opcode.Value() == OpCode::Id::DP3) ? 3 : 4; for (int i = 0; i < num_components; ++i) dot = dot + src1[i] * src2[i]; @@ -253,7 +257,7 @@ static void ProcessShaderCode(VertexShaderState& state) { } // Reciprocal - case Instruction::OpCode::RCP: + case OpCode::Id::RCP: { for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -268,7 +272,7 @@ static void ProcessShaderCode(VertexShaderState& state) { } // Reciprocal Square Root - case Instruction::OpCode::RSQ: + case OpCode::Id::RSQ: { for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -282,7 +286,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::MOVA: + case OpCode::Id::MOVA: { for (int i = 0; i < 2; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -295,7 +299,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::MOV: + case OpCode::Id::MOV: { for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) @@ -306,7 +310,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::CMP: + case OpCode::Id::CMP: for (int i = 0; i < 2; ++i) { // TODO: Can you restrict to one compare via dest masking? @@ -347,7 +351,7 @@ static void ProcessShaderCode(VertexShaderState& state) { default: LOG_ERROR(HW_GPU, "Unhandled arithmetic instruction: 0x%02x (%s): 0x%08x", - (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); + (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); DEBUG_ASSERT(false); break; } @@ -355,9 +359,9 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCodeType::MultiplyAdd: + case OpCode::Type::MultiplyAdd: { - if (instr.opcode.EffectiveOpCode() == Instruction::OpCode::MAD) { + if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD) { const SwizzlePattern& swizzle = *(SwizzlePattern*)&swizzle_data[instr.mad.operand_desc_id]; const float24* src1_ = LookupSourceRegister(instr.mad.src1); @@ -405,9 +409,9 @@ static void ProcessShaderCode(VertexShaderState& state) { src3[3] = src3[3] * float24::FromFloat32(-1); } - float24* dest = (instr.mad.dest < 0x08) ? state.output_register_table[4*instr.mad.dest.GetIndex()] - : (instr.mad.dest < 0x10) ? dummy_vec4_float24 - : (instr.mad.dest < 0x20) ? &state.temporary_registers[instr.mad.dest.GetIndex()][0] + float24* dest = (instr.mad.dest.Value() < 0x08) ? state.output_register_table[4*instr.mad.dest.Value().GetIndex()] + : (instr.mad.dest.Value() < 0x10) ? dummy_vec4_float24 + : (instr.mad.dest.Value() < 0x20) ? &state.temporary_registers[instr.mad.dest.Value().GetIndex()][0] : dummy_vec4_float24; for (int i = 0; i < 4; ++i) { @@ -418,7 +422,7 @@ static void ProcessShaderCode(VertexShaderState& state) { } } else { LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x%02x (%s): 0x%08x", - (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); + (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); } break; } @@ -445,31 +449,31 @@ static void ProcessShaderCode(VertexShaderState& state) { }; // Handle each instruction on its own - switch (instr.opcode) { - case Instruction::OpCode::END: + switch (instr.opcode.Value()) { + case OpCode::Id::END: exit_loop = true; break; - case Instruction::OpCode::JMPC: + case OpCode::Id::JMPC: if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { state.program_counter = &shader_memory[instr.flow_control.dest_offset] - 1; } break; - case Instruction::OpCode::JMPU: + case OpCode::Id::JMPU: if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { state.program_counter = &shader_memory[instr.flow_control.dest_offset] - 1; } break; - case Instruction::OpCode::CALL: + case OpCode::Id::CALL: call(state, instr.flow_control.dest_offset, instr.flow_control.num_instructions, binary_offset + 1, 0, 0); break; - case Instruction::OpCode::CALLU: + case OpCode::Id::CALLU: if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { call(state, instr.flow_control.dest_offset, @@ -478,7 +482,7 @@ static void ProcessShaderCode(VertexShaderState& state) { } break; - case Instruction::OpCode::CALLC: + case OpCode::Id::CALLC: if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { call(state, instr.flow_control.dest_offset, @@ -487,10 +491,10 @@ static void ProcessShaderCode(VertexShaderState& state) { } break; - case Instruction::OpCode::NOP: + case OpCode::Id::NOP: break; - case Instruction::OpCode::IFU: + case OpCode::Id::IFU: if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { call(state, binary_offset + 1, @@ -505,7 +509,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; - case Instruction::OpCode::IFC: + case OpCode::Id::IFC: { // TODO: Do we need to consider swizzlers here? @@ -524,7 +528,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } - case Instruction::OpCode::LOOP: + case OpCode::Id::LOOP: { state.address_registers[2] = shader_uniforms.i[instr.flow_control.int_uniform_id].y; @@ -539,7 +543,7 @@ static void ProcessShaderCode(VertexShaderState& state) { default: LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", - (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); + (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); break; } diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 0a236595c..b9d4ede3a 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -18,7 +18,6 @@ namespace VideoCore { EmuWindow* g_emu_window = nullptr; ///< Frontend emulator window RendererBase* g_renderer = nullptr; ///< Renderer plugin -int g_current_frame = 0; /// Initialize the video core void Init(EmuWindow* emu_window) { @@ -27,8 +26,6 @@ void Init(EmuWindow* emu_window) { g_renderer->SetWindow(g_emu_window); g_renderer->Init(); - g_current_frame = 0; - LOG_DEBUG(Render, "initialized OK"); } diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index b782f17bd..1b51d39bf 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -30,7 +30,6 @@ static const int kScreenBottomHeight = 240; ///< 3DS bottom screen height // --------------------- extern RendererBase* g_renderer; ///< Renderer plugin -extern int g_current_frame; ///< Current frame extern EmuWindow* g_emu_window; ///< Emu window /// Start the video core |