Jelajahi Sumber

Fix merge conflicts

darkf 11 tahun lalu
induk
melakukan
8ba9ac0f74
100 mengubah file dengan 3017 tambahan dan 1280 penghapusan
  1. 16 0
      .gitignore
  2. 6 0
      .gitmodules
  3. 1 0
      .travis-build.sh
  4. 10 3
      .travis-deps.sh
  5. 5 0
      .travis.yml
  6. 36 6
      CMakeLists.txt
  7. 1 0
      CONTRIBUTING.md
  8. 1 1
      Doxyfile
  9. 18 4
      README.md
  10. 1 0
      externals/boost
  11. 1 0
      externals/nihstro
  12. 6 8
      src/citra/CMakeLists.txt
  13. 17 8
      src/citra/citra.cpp
  14. 13 17
      src/citra/config.cpp
  15. 2 5
      src/citra/config.h
  16. 4 3
      src/citra/default_ini.h
  17. 15 11
      src/citra/emu_window/emu_window_glfw.cpp
  18. 1 1
      src/citra/emu_window/emu_window_glfw.h
  19. 11 0
      src/citra_qt/CMakeLists.txt
  20. 16 7
      src/citra_qt/bootmanager.cpp
  21. 21 37
      src/citra_qt/config.cpp
  22. 3 10
      src/citra_qt/config.h
  23. 2 2
      src/citra_qt/debugger/callstack.cpp
  24. 3 1
      src/citra_qt/debugger/disassembler.cpp
  25. 1 1
      src/citra_qt/debugger/graphics.cpp
  26. 1 1
      src/citra_qt/debugger/graphics.hxx
  27. 263 0
      src/citra_qt/debugger/graphics_breakpoints.cpp
  28. 53 0
      src/citra_qt/debugger/graphics_breakpoints.hxx
  29. 38 0
      src/citra_qt/debugger/graphics_breakpoints_p.hxx
  30. 255 20
      src/citra_qt/debugger/graphics_cmdlists.cpp
  31. 38 1
      src/citra_qt/debugger/graphics_cmdlists.hxx
  32. 283 0
      src/citra_qt/debugger/graphics_framebuffer.cpp
  33. 92 0
      src/citra_qt/debugger/graphics_framebuffer.hxx
  34. 2 2
      src/citra_qt/hotkeys.cpp
  35. 45 19
      src/citra_qt/main.cpp
  36. 303 0
      src/citra_qt/util/spinbox.cpp
  37. 88 0
      src/citra_qt/util/spinbox.hxx
  38. 10 4
      src/common/CMakeLists.txt
  39. 12 2
      src/common/bit_field.h
  40. 3 3
      src/common/break_points.cpp
  41. 2 2
      src/common/break_points.h
  42. 41 18
      src/common/chunk_file.h
  43. 2 2
      src/common/common.h
  44. 8 3
      src/common/common_funcs.h
  45. 8 15
      src/common/common_paths.h
  46. 0 2
      src/common/common_types.h
  47. 164 0
      src/common/concurrent_ring_buffer.h
  48. 0 319
      src/common/console_listener.cpp
  49. 0 38
      src/common/console_listener.h
  50. 2 2
      src/common/cpu_detect.h
  51. 1 1
      src/common/emu_window.cpp
  52. 1 1
      src/common/emu_window.h
  53. 12 12
      src/common/extended_trace.cpp
  54. 2 2
      src/common/fifo_queue.h
  55. 2 2
      src/common/file_search.cpp
  56. 2 2
      src/common/file_search.h
  57. 61 69
      src/common/file_util.cpp
  58. 7 4
      src/common/file_util.h
  59. 2 2
      src/common/hash.cpp
  60. 2 2
      src/common/hash.h
  61. 1 1
      src/common/key_map.cpp
  62. 1 1
      src/common/key_map.h
  63. 3 3
      src/common/linear_disk_cache.h
  64. 12 121
      src/common/log.h
  65. 0 199
      src/common/log_manager.cpp
  66. 0 166
      src/common/log_manager.h
  67. 151 0
      src/common/logging/backend.cpp
  68. 134 0
      src/common/logging/backend.h
  69. 132 0
      src/common/logging/filter.cpp
  70. 63 0
      src/common/logging/filter.h
  71. 115 0
      src/common/logging/log.h
  72. 136 0
      src/common/logging/text_formatter.cpp
  73. 41 0
      src/common/logging/text_formatter.h
  74. 16 0
      src/common/make_unique.h
  75. 2 2
      src/common/math_util.cpp
  76. 2 2
      src/common/math_util.h
  77. 17 17
      src/common/mem_arena.cpp
  78. 8 8
      src/common/memory_util.cpp
  79. 2 2
      src/common/memory_util.h
  80. 4 4
      src/common/misc.cpp
  81. 3 3
      src/common/msg_handler.cpp
  82. 2 2
      src/common/msg_handler.h
  83. 1 1
      src/common/platform.h
  84. 1 1
      src/common/scm_rev.h
  85. 37 0
      src/common/scope_exit.h
  86. 16 16
      src/common/string_util.cpp
  87. 17 2
      src/common/string_util.h
  88. 1 1
      src/common/symbols.cpp
  89. 1 1
      src/common/symbols.h
  90. 2 2
      src/common/thread.cpp
  91. 3 2
      src/common/thread.h
  92. 7 7
      src/common/thread_queue_list.h
  93. 2 2
      src/common/thunk.h
  94. 5 5
      src/common/timer.cpp
  95. 2 2
      src/common/timer.h
  96. 12 12
      src/common/utf8.cpp
  97. 40 17
      src/core/CMakeLists.txt
  98. 7 1
      src/core/arm/arm_interface.h
  99. 1 1
      src/core/arm/disassembler/load_symbol_map.cpp
  100. 1 1
      src/core/arm/disassembler/load_symbol_map.h

+ 16 - 0
.gitignore

@@ -8,3 +8,19 @@ src/common/scm_rev.cpp
 # Project/editor files
 *.swp
 .idea/
+
+# *nix related
+# Common convention for backup or temporary files
+*~
+
+# OSX global filetypes
+# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
+.DS_Store
+.AppleDouble
+.LSOverride
+.Spotlight-V100
+.Trashes
+
+# Windows global filetypes
+Thumbs.db
+

+ 6 - 0
.gitmodules

@@ -1,3 +1,9 @@
 [submodule "externals/inih/inih"]
 	path = externals/inih/inih
 	url = https://github.com/svn2github/inih
+[submodule "externals/boost"]
+	path = externals/boost
+	url = https://github.com/citra-emu/ext-boost.git
+[submodule "externals/nihstro"]
+	path = externals/nihstro
+	url = https://github.com/neobrain/nihstro.git

+ 1 - 0
.travis-build.sh

@@ -1,6 +1,7 @@
 #!/bin/sh
 
 set -e
+set -x
 
 #if OS is linux or is not set
 if [ "$TRAVIS_OS_NAME" = linux -o -z "$TRAVIS_OS_NAME" ]; then 

+ 10 - 3
.travis-deps.sh

@@ -1,17 +1,22 @@
 #!/bin/sh
 
 set -e
+set -x
 
 #if OS is linux or is not set
 if [ "$TRAVIS_OS_NAME" = linux -o -z "$TRAVIS_OS_NAME" ]; then
     sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
     sudo apt-get -qq update
-    sudo apt-get -qq install g++-4.8 xorg-dev libglu1-mesa-dev libxcursor-dev
-    sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 90
+    sudo apt-get -qq install g++-4.9 xorg-dev libglu1-mesa-dev libxcursor-dev
+    sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 90
     (
         git clone https://github.com/glfw/glfw.git --branch 3.0.4 --depth 1
         mkdir glfw/build && cd glfw/build
-        cmake .. && make -j2 && sudo make install
+        cmake -DBUILD_SHARED_LIBS=ON \
+              -DGLFW_BUILD_EXAMPLES=OFF \
+              -DGLFW_BUILD_TESTS=OFF \
+              ..
+        make -j4 && sudo make install
     )
 
     sudo apt-get install lib32stdc++6
@@ -19,6 +24,8 @@ if [ "$TRAVIS_OS_NAME" = linux -o -z "$TRAVIS_OS_NAME" ]; then
     curl http://www.cmake.org/files/v2.8/cmake-2.8.11-Linux-i386.tar.gz \
         | sudo tar -xz -C /usr/local --strip-components=1
 elif [ "$TRAVIS_OS_NAME" = osx ]; then
+    export HOMEBREW_CACHE="$PWD/.homebrew-cache"
+    mkdir -p "$HOMEBREW_CACHE"
     brew tap homebrew/versions
     brew install qt5 glfw3 pkgconfig
 fi

+ 5 - 0
.travis.yml

@@ -4,6 +4,11 @@ os:
 
 language: cpp
 
+cache:
+  apt: true
+  directories:
+    - .homebrew-cache
+
 before_install:
  - sh .travis-deps.sh
 

+ 36 - 6
CMakeLists.txt

@@ -6,16 +6,48 @@ project(citra)
 
 if (NOT MSVC)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-attributes")
+    add_definitions(-pthread)
 else()
     # Silence deprecation warnings
     add_definitions(/D_CRT_SECURE_NO_WARNINGS)
+    # set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
+    # Tweak optimization settings
+    # As far as I can tell, there's no way to override the CMake defaults while leaving user
+    # changes intact, so we'll just clobber everything and say sorry.
+    message(STATUS "Cache compiler flags ignored, please edit CMakeFiles.txt to change the flags.")
+    # /MD - Multi-threaded runtime
+    # /Ox - Full optimization
+    # /Oi - Use intrinsic functions
+    # /Oy- - Don't omit frame pointer
+    # /GR- - Disable RTTI
+    # /GS- - No stack buffer overflow checks
+    # /EHsc - C++-only exception handling semantics
+    set(optimization_flags "/MD /Ox /Oi /Oy- /DNDEBUG /GR- /GS- /EHsc")
+    # /Zi - Output debugging information
+    # /Zo - enahnced debug info for optimized builds
+    set(CMAKE_C_FLAGS_RELEASE   "${optimization_flags} /Zi" CACHE STRING "" FORCE)
+    set(CMAKE_CXX_FLAGS_RELEASE "${optimization_flags} /Zi" CACHE STRING "" FORCE)
+    set(CMAKE_C_FLAGS_RELWITHDEBINFO   "${optimization_flags} /Zi /Zo" CACHE STRING "" FORCE)
+    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${optimization_flags} /Zi /Zo" CACHE STRING "" FORCE)
 endif()
 add_definitions(-DSINGLETHREADED)
 
-find_package(PNG)
+find_package(PNG QUIET)
 if (PNG_FOUND)
     add_definitions(-DHAVE_PNG)
-endif ()
+else()
+    message(STATUS "libpng not found. Some debugging features have been disabled.")
+endif()
+
+find_package(Boost 1.57.0)
+if (Boost_FOUND)
+    include_directories(${Boost_INCLUDE_DIRS})
+else()
+    message(STATUS "Boost 1.57.0 or newer not found, falling back to externals")
+    include_directories(externals/boost)
+endif()
 
 # Include bundled CMake modules
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
@@ -65,10 +97,6 @@ if (ENABLE_GLFW)
 
         set(GLFW_LIBRARIES glfw3)
     else()
-        if (NOT APPLE)
-            find_package(X11 REQUIRED)
-        endif()
-
         find_package(PkgConfig REQUIRED)
         pkg_search_module(GLFW REQUIRED glfw3)
     endif()
@@ -131,6 +159,8 @@ set(INI_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/externals/inih")
 include_directories(${INI_PREFIX})
 add_subdirectory(${INI_PREFIX})
 
+include_directories(externals/nihstro/include)
+
 # process subdirectories
 if(ENABLE_QT)
     include_directories(externals/qhexedit)

+ 1 - 0
CONTRIBUTING.md

@@ -27,6 +27,7 @@ Follow the indentation/whitespace style shown below. Do not use tabs, use 4-spac
 ### Comments
 * For regular comments, use C++ style (`//`) comments, even for multi-line ones.
 * For doc-comments (Doxygen comments), use `/// ` if it's a single line, else use the `/**` `*/` style featured in the example. Start the text on the second line, not the first containing `/**`.
+* For items that are both defined and declared in two separate files, put the doc-comment only next to the associated declaration. (In a header file, usually.) Otherwise, put it next to the implementation. Never duplicate doc-comments in both places.
 
 ```cpp
 namespace Example {

+ 1 - 1
Doxyfile

@@ -419,7 +419,7 @@ LOOKUP_CACHE_SIZE      = 0
 # normally produced when WARNINGS is set to YES.
 # The default value is: NO.
 
-EXTRACT_ALL            = NO
+EXTRACT_ALL            = YES
 
 # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
 # be included in the documentation.

+ 18 - 4
README.md

@@ -1,17 +1,31 @@
-citra emulator
+Citra Emulator
 ==============
 [![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg)](https://travis-ci.org/citra-emu/citra)
 
-An experimental open-source Nintendo 3DS emulator/debugger written in C++. Citra is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. At this time, it only emulates a subset of 3DS hardware, and therefore is generally only useful for booting/debugging very simple homebrew demos. 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.
+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.
 
-For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/).
+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.
+
+For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/?channels=citra).
 
 ### Development
 
-If you want to contribute please take a took at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator.
+If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator.
 
 ### Building
 
 * __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Windows-Build)
 * __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Linux-Build)
 * __OSX__: [OS X Build](https://github.com/citra-emu/citra/wiki/OS-X-Build)
+
+
+### Support
+If you like, you can [donate by PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K899FANUJ2ZXW) - any donation received will go towards things like:
+* 3DS consoles for developers to explore the hardware
+* 3DS games for testing
+* Any equipment required for homebrew
+* Infrastructure setup
+* Eventually 3D displays to get proper 3D output working
+* ... etc ...
+ 
+We also more than gladly accept used 3DS consoles, preferrably ones with firmware 4.5 or lower! If you would like to give yours away, don't hesitate to join our IRC channel #citra on [Freenode](http://webchat.freenode.net/?channels=citra) and talk to neobrain or bunnei. Mind you, IRC is slow-paced, so it might be a while until people reply. If you're in a hurry you can just leave contact details in the channel or via private message and we'll get back to you.

+ 1 - 0
externals/boost

@@ -0,0 +1 @@
+Subproject commit 97052c28acb141dbf3c5e14114af99045344b695

+ 1 - 0
externals/nihstro

@@ -0,0 +1 @@
+Subproject commit fc71f8684d26ccf277ad68809c8bd7273141fe89

+ 6 - 8
src/citra/CMakeLists.txt

@@ -12,25 +12,23 @@ set(HEADERS
 
 create_directory_groups(${SRCS} ${HEADERS})
 
-# NOTE: This is a workaround for CMake bug 0006976 (missing X11_xf86vmode_LIB variable)
-if (NOT X11_xf86vmode_LIB)
-    set(X11_xv86vmode_LIB Xxf86vm)
-endif()
-
 add_executable(citra ${SRCS} ${HEADERS})
 target_link_libraries(citra core common video_core)
 target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih)
 
+if (UNIX)
+    target_link_libraries(citra -pthread)
+endif()
+
 if (APPLE)
-    target_link_libraries(citra iconv pthread ${COREFOUNDATION_LIBRARY})
+    target_link_libraries(citra iconv ${COREFOUNDATION_LIBRARY})
 elseif (WIN32)
     target_link_libraries(citra winmm)
     if (MINGW)
         target_link_libraries(citra iconv)
     endif()    
 else() # Unix
-    target_link_libraries(citra pthread rt)
-    target_link_libraries(citra ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_Xcursor_LIB} ${X11_Xrandr_LIB} ${X11_xv86vmode_LIB})
+    target_link_libraries(citra rt)
 endif()
 
 #install(TARGETS citra RUNTIME DESTINATION ${bindir})

+ 17 - 8
src/citra/citra.cpp

@@ -1,9 +1,14 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <thread>
+
 #include "common/common.h"
-#include "common/log_manager.h"
+#include "common/logging/text_formatter.h"
+#include "common/logging/backend.h"
+#include "common/logging/filter.h"
+#include "common/scope_exit.h"
 
 #include "core/settings.h"
 #include "core/system.h"
@@ -15,17 +20,21 @@
 
 /// Application entry point
 int __cdecl main(int argc, char **argv) {
-    LogManager::Init();
+    std::shared_ptr<Log::Logger> logger = Log::InitGlobalLogger();
+    Log::Filter log_filter(Log::Level::Debug);
+    std::thread logging_thread(Log::TextLoggingLoop, logger, &log_filter);
+    SCOPE_EXIT({
+        logger->Close();
+        logging_thread.join();
+    });
 
     if (argc < 2) {
-        ERROR_LOG(BOOT, "Failed to load ROM: No ROM specified");
+        LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
         return -1;
     }
 
     Config config;
-
-    if (!Settings::values.enable_log)
-        LogManager::Shutdown();
+    log_filter.ParseFilterString(Settings::values.log_filter);
 
     std::string boot_filename = argv[1];
     EmuWindow_GLFW* emu_window = new EmuWindow_GLFW;
@@ -34,7 +43,7 @@ int __cdecl main(int argc, char **argv) {
 
     Loader::ResultStatus load_result = Loader::LoadFile(boot_filename);
     if (Loader::ResultStatus::Success != load_result) {
-        ERROR_LOG(BOOT, "Failed to load ROM (Error %i)!", load_result);
+        LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result);
         return -1;
     }
 

+ 13 - 17
src/citra/config.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include <GLFW/glfw3.h>
@@ -22,21 +22,22 @@ Config::Config() {
 bool Config::LoadINI(INIReader* config, const char* location, const std::string& default_contents, bool retry) {
     if (config->ParseError() < 0) {
         if (retry) {
-            ERROR_LOG(CONFIG, "Failed to load %s. Creating file from defaults...", location);
+            LOG_WARNING(Config, "Failed to load %s. Creating file from defaults...", location);
             FileUtil::CreateFullPath(location);
             FileUtil::WriteStringToFile(true, default_contents, location);
             *config = INIReader(location); // Reopen file
 
             return LoadINI(config, location, default_contents, false);
         }
-        ERROR_LOG(CONFIG, "Failed.");
+        LOG_ERROR(Config, "Failed.");
         return false;
     }
-    INFO_LOG(CONFIG, "Successfully loaded %s", location);
+    LOG_INFO(Config, "Successfully loaded %s", location);
     return true;
 }
 
-void Config::ReadControls() {
+void Config::ReadValues() {
+    // Controls
     Settings::values.pad_a_key = glfw_config->GetInteger("Controls", "pad_a", GLFW_KEY_A);
     Settings::values.pad_b_key = glfw_config->GetInteger("Controls", "pad_b", GLFW_KEY_S);
     Settings::values.pad_x_key = glfw_config->GetInteger("Controls", "pad_x", GLFW_KEY_Z);
@@ -54,27 +55,22 @@ void Config::ReadControls() {
     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);
-}
 
-void Config::ReadCore() {
+    // Core
     Settings::values.cpu_core = glfw_config->GetInteger("Core", "cpu_core", Core::CPU_Interpreter);
-    Settings::values.gpu_refresh_rate = glfw_config->GetInteger("Core", "gpu_refresh_rate", 60);
-}
+    Settings::values.gpu_refresh_rate = glfw_config->GetInteger("Core", "gpu_refresh_rate", 30);
+    Settings::values.frame_skip = glfw_config->GetInteger("Core", "frame_skip", 0);
 
-void Config::ReadData() {
+    // Data Storage
     Settings::values.use_virtual_sd = glfw_config->GetBoolean("Data Storage", "use_virtual_sd", true);
-}
 
-void Config::ReadMiscellaneous() {
-    Settings::values.enable_log = glfw_config->GetBoolean("Miscellaneous", "enable_log", true);
+    // Miscellaneous
+    Settings::values.log_filter = glfw_config->Get("Miscellaneous", "log_filter", "*:Info");
 }
 
 void Config::Reload() {
     LoadINI(glfw_config, glfw_config_loc.c_str(), DefaultINI::glfw_config_file);
-    ReadControls();
-    ReadCore();
-    ReadData();
-    ReadMiscellaneous();
+    ReadValues();
 }
 
 Config::~Config() {

+ 2 - 5
src/citra/config.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -15,10 +15,7 @@ class Config {
     std::string glfw_config_loc;
 
     bool LoadINI(INIReader* config, const char* location, const std::string& default_contents="", bool retry=true);
-    void ReadControls();
-    void ReadCore();
-    void ReadData();
-    void ReadMiscellaneous();
+    void ReadValues();
 public:
     Config();
     ~Config();

+ 4 - 3
src/citra/default_ini.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -28,13 +28,14 @@ pad_sright =
 
 [Core]
 cpu_core = ## 0: Interpreter (default), 1: FastInterpreter (experimental)
-gpu_refresh_rate = ## 60 (default)
+gpu_refresh_rate = ## 30 (default)
+frame_skip = ## 0: No frameskip (default), 1 : 2x frameskip, 2 : 4x frameskip, etc.
 
 [Data Storage]
 use_virtual_sd =
 
 [Miscellaneous]
-enable_log =
+log_filter = *:Info  ## Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
 )";
 
 }

+ 15 - 11
src/citra/emu_window/emu_window_glfw.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include <GLFW/glfw3.h>
@@ -36,15 +36,15 @@ const bool EmuWindow_GLFW::IsOpen() {
 }
 
 void EmuWindow_GLFW::OnFramebufferResizeEvent(GLFWwindow* win, int width, int height) {
-    _dbg_assert_(GUI, width > 0);
-    _dbg_assert_(GUI, height > 0);
+    _dbg_assert_(Frontend, width > 0);
+    _dbg_assert_(Frontend, height > 0);
 
     GetEmuWindow(win)->NotifyFramebufferSizeChanged(std::pair<unsigned,unsigned>(width, height));
 }
 
 void EmuWindow_GLFW::OnClientAreaResizeEvent(GLFWwindow* win, int width, int height) {
-    _dbg_assert_(GUI, width > 0);
-    _dbg_assert_(GUI, height > 0);
+    _dbg_assert_(Frontend, width > 0);
+    _dbg_assert_(Frontend, height > 0);
 
     // NOTE: GLFW provides no proper way to set a minimal window size.
     //       Hence, we just ignore the corresponding EmuWindow hint.
@@ -58,9 +58,13 @@ EmuWindow_GLFW::EmuWindow_GLFW() {
 
     ReloadSetKeymaps();
 
+    glfwSetErrorCallback([](int error, const char *desc){
+        LOG_ERROR(Frontend, "GLFW 0x%08x: %s", error, desc);
+    });
+
     // Initialize the window
     if(glfwInit() != GL_TRUE) {
-        printf("Failed to initialize GLFW! Exiting...");
+        LOG_CRITICAL(Frontend, "Failed to initialize GLFW! Exiting...");
         exit(1);
     }
     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
@@ -72,10 +76,10 @@ EmuWindow_GLFW::EmuWindow_GLFW() {
     std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
     m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth,
         (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight),
-        window_title.c_str(), NULL, NULL);
+        window_title.c_str(), nullptr, nullptr);
 
-    if (m_render_window == NULL) {
-        printf("Failed to create GLFW window! Exiting...");
+    if (m_render_window == nullptr) {
+        LOG_CRITICAL(Frontend, "Failed to create GLFW window! Exiting...");
         exit(1);
     }
 
@@ -119,7 +123,7 @@ void EmuWindow_GLFW::MakeCurrent() {
 
 /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
 void EmuWindow_GLFW::DoneCurrent() {
-    glfwMakeContextCurrent(NULL);
+    glfwMakeContextCurrent(nullptr);
 }
 
 void EmuWindow_GLFW::ReloadSetKeymaps() {
@@ -145,7 +149,7 @@ void EmuWindow_GLFW::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,u
     std::pair<int,int> current_size;
     glfwGetWindowSize(m_render_window, &current_size.first, &current_size.second);
 
-    _dbg_assert_(GUI, (int)minimal_size.first > 0 && (int)minimal_size.second > 0);
+    _dbg_assert_(Frontend, (int)minimal_size.first > 0 && (int)minimal_size.second > 0);
     int new_width  = std::max(current_size.first,  (int)minimal_size.first);
     int new_height = std::max(current_size.second, (int)minimal_size.second);
 

+ 1 - 1
src/citra/emu_window/emu_window_glfw.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 11 - 0
src/citra_qt/CMakeLists.txt

@@ -8,9 +8,12 @@ set(SRCS
             debugger/callstack.cpp
             debugger/disassembler.cpp
             debugger/graphics.cpp
+            debugger/graphics_breakpoints.cpp
             debugger/graphics_cmdlists.cpp
+            debugger/graphics_framebuffer.cpp
             debugger/ramview.cpp
             debugger/registers.cpp
+            util/spinbox.cpp
             bootmanager.cpp
             hotkeys.cpp
             main.cpp
@@ -23,9 +26,13 @@ set(HEADERS
             debugger/callstack.hxx
             debugger/disassembler.hxx
             debugger/graphics.hxx
+            debugger/graphics_breakpoints.hxx
+            debugger/graphics_breakpoints_p.hxx
             debugger/graphics_cmdlists.hxx
+            debugger/graphics_framebuffer.hxx
             debugger/ramview.hxx
             debugger/registers.hxx
+            util/spinbox.hxx
             bootmanager.hxx
             hotkeys.hxx
             main.hxx
@@ -53,6 +60,10 @@ add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
 target_link_libraries(citra-qt core common video_core qhexedit)
 target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
 
+if (UNIX)
+    target_link_libraries(citra-qt -pthread)
+endif()
+
 if (APPLE)
     target_link_libraries(citra-qt iconv ${COREFOUNDATION_LIBRARY})
 elseif (WIN32)

+ 16 - 7
src/citra_qt/bootmanager.cpp

@@ -14,6 +14,8 @@
 #include "core/core.h"
 #include "core/settings.h"
 
+#include "video_core/debug_utils/debug_utils.h"
+
 #include "video_core/video_core.h"
 
 #include "citra_qt/version.h"
@@ -60,26 +62,33 @@ void EmuThread::Stop()
 {
     if (!isRunning())
     {
-        INFO_LOG(MASTER_LOG, "EmuThread::Stop called while emu thread wasn't running, returning...");
+        LOG_WARNING(Frontend, "EmuThread::Stop called while emu thread wasn't running, returning...");
         return;
     }
     stop_run = true;
 
+    // Release emu threads from any breakpoints, so that this doesn't hang forever.
+    Pica::g_debug_context->ClearBreakpoints();
+
     //core::g_state = core::SYS_DIE;
 
-    wait(500);
+    // TODO: Waiting here is just a bad workaround for retarded shutdown logic.
+    wait(1000);
     if (isRunning())
     {
-        WARN_LOG(MASTER_LOG, "EmuThread still running, terminating...");
+        LOG_WARNING(Frontend, "EmuThread still running, terminating...");
         quit();
-        wait(1000);
+
+        // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam
+        // queued... This should be fixed.
+        wait(50000);
         if (isRunning())
         {
-            WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here...");
+            LOG_CRITICAL(Frontend, "EmuThread STILL running, something is wrong here...");
             terminate();
         }
     }
-    INFO_LOG(MASTER_LOG, "EmuThread stopped");
+    LOG_INFO(Frontend, "EmuThread stopped");
 }
 
 
@@ -230,7 +239,7 @@ QByteArray GRenderWindow::saveGeometry()
 {
     // If we are a top-level widget, store the current geometry
     // otherwise, store the last backup
-    if (parent() == NULL)
+    if (parent() == nullptr)
         return ((QGLWidget*)this)->saveGeometry();
     else
         return geometry;

+ 21 - 37
src/citra_qt/config.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include <QString>
@@ -21,7 +21,7 @@ Config::Config() {
     Reload();
 }
 
-void Config::ReadControls() {
+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();
@@ -41,9 +41,23 @@ void Config::ReadControls() {
     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();
     qt_config->endGroup();
+
+    qt_config->beginGroup("Core");
+    Settings::values.cpu_core = qt_config->value("cpu_core", Core::CPU_Interpreter).toInt();
+    Settings::values.gpu_refresh_rate = qt_config->value("gpu_refresh_rate", 30).toInt();
+    Settings::values.frame_skip = qt_config->value("frame_skip", 0).toInt();
+    qt_config->endGroup();
+
+    qt_config->beginGroup("Data Storage");
+    Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
+    qt_config->endGroup();
+
+    qt_config->beginGroup("Miscellaneous");
+    Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
+    qt_config->endGroup();
 }
 
-void Config::SaveControls() {
+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);
@@ -63,58 +77,28 @@ void Config::SaveControls() {
     qt_config->setValue("pad_sleft",  Settings::values.pad_sleft_key);
     qt_config->setValue("pad_sright", Settings::values.pad_sright_key);
     qt_config->endGroup();
-}
-
-void Config::ReadCore() {
-    qt_config->beginGroup("Core");
-    Settings::values.cpu_core = qt_config->value("cpu_core", Core::CPU_Interpreter).toInt();
-    Settings::values.gpu_refresh_rate = qt_config->value("gpu_refresh_rate", 60).toInt();
-    qt_config->endGroup();
-}
 
-void Config::SaveCore() {
     qt_config->beginGroup("Core");
     qt_config->setValue("cpu_core", Settings::values.cpu_core);
     qt_config->setValue("gpu_refresh_rate", Settings::values.gpu_refresh_rate);
+    qt_config->setValue("frame_skip", Settings::values.frame_skip);
     qt_config->endGroup();
-}
-
-void Config::ReadData() {
-    qt_config->beginGroup("Data Storage");
-    Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
-    qt_config->endGroup();
-}
 
-void Config::SaveData() {
     qt_config->beginGroup("Data Storage");
     qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
     qt_config->endGroup();
-}
-
-void Config::ReadMiscellaneous() {
-    qt_config->beginGroup("Miscellaneous");
-    Settings::values.enable_log = qt_config->value("enable_log", true).toBool();
-    qt_config->endGroup();
-}
 
-void Config::SaveMiscellaneous() {
     qt_config->beginGroup("Miscellaneous");
-    qt_config->setValue("enable_log", Settings::values.enable_log);
+    qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
     qt_config->endGroup();
 }
 
 void Config::Reload() {
-    ReadControls();
-    ReadCore();
-    ReadData();
-    ReadMiscellaneous();
+    ReadValues();
 }
 
 void Config::Save() {
-    SaveControls();
-    SaveCore();
-    SaveData();
-    SaveMiscellaneous();
+    SaveValues();
 }
 
 Config::~Config() {

+ 3 - 10
src/citra_qt/config.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -12,15 +12,8 @@ class Config {
     QSettings* qt_config;
     std::string qt_config_loc;
 
-    void ReadControls();
-    void SaveControls();
-    void ReadCore();
-    void SaveCore();
-    void ReadData();
-    void SaveData();
-
-    void ReadMiscellaneous();
-    void SaveMiscellaneous();
+    void ReadValues();
+    void SaveValues();
 public:
     Config();
     ~Config();

+ 2 - 2
src/citra_qt/debugger/callstack.cpp

@@ -27,10 +27,10 @@ void CallstackWidget::OnCPUStepped()
     ARM_Interface* app_core = Core::g_app_core;
 
     u32 sp = app_core->GetReg(13); //stack pointer
-    u32 addr, ret_addr, call_addr, func_addr;
+    u32 ret_addr, call_addr, func_addr;
 
     int counter = 0;
-    for (int addr = 0x10000000; addr >= sp; addr -= 4)
+    for (u32 addr = 0x10000000; addr >= sp; addr -= 4)
     {
         ret_addr = Memory::Read32(addr);
         call_addr = ret_addr - 4; //get call address???

+ 3 - 1
src/citra_qt/debugger/disassembler.cpp

@@ -220,7 +220,9 @@ void DisassemblerWidget::OnPause()
     emu_thread.SetCpuRunning(false);
 
     // TODO: By now, the CPU might not have actually stopped...
-    model->SetNextInstruction(Core::g_app_core->GetPC());
+    if (Core::g_app_core) {
+        model->SetNextInstruction(Core::g_app_core->GetPC());
+    }
 }
 
 void DisassemblerWidget::OnToggleStartStop()

+ 1 - 1
src/citra_qt/debugger/graphics.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include "graphics.hxx"

+ 1 - 1
src/citra_qt/debugger/graphics.hxx

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 263 - 0
src/citra_qt/debugger/graphics_breakpoints.cpp

@@ -0,0 +1,263 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMetaType>
+#include <QPushButton>
+#include <QTreeWidget>
+#include <QVBoxLayout>
+#include <QLabel>
+
+#include "graphics_breakpoints.hxx"
+#include "graphics_breakpoints_p.hxx"
+
+BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
+    : QAbstractListModel(parent), context_weak(debug_context),
+      at_breakpoint(debug_context->at_breakpoint),
+      active_breakpoint(debug_context->active_breakpoint)
+{
+
+}
+
+int BreakPointModel::columnCount(const QModelIndex& parent) const
+{
+    return 2;
+}
+
+int BreakPointModel::rowCount(const QModelIndex& parent) const
+{
+    return static_cast<int>(Pica::DebugContext::Event::NumEvents);
+}
+
+QVariant BreakPointModel::data(const QModelIndex& index, int role) const
+{
+    const auto event = static_cast<Pica::DebugContext::Event>(index.row());
+
+    switch (role) {
+    case Qt::DisplayRole:
+    {
+        switch (index.column()) {
+        case 0:
+        {
+            static const std::map<Pica::DebugContext::Event, QString> map = {
+                { Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded") },
+                { Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed") },
+                { Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") },
+                { Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") },
+                { Pica::DebugContext::Event::VertexLoaded, tr("Vertex Loaded") }
+            };
+
+            _dbg_assert_(Debug_GPU, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents));
+
+            return (map.find(event) != map.end()) ? map.at(event) : QString();
+        }
+
+        case 1:
+            return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled");
+
+        default:
+            break;
+        }
+
+        break;
+    }
+
+    case Qt::BackgroundRole:
+    {
+        if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
+            return QBrush(QColor(0xE0, 0xE0, 0x10));
+        }
+        break;
+    }
+
+    case Role_IsEnabled:
+    {
+        auto context = context_weak.lock();
+        return context && context->breakpoints[event].enabled;
+    }
+
+    default:
+        break;
+    }
+    return QVariant();
+}
+
+QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    switch(role) {
+    case Qt::DisplayRole:
+    {
+        if (section == 0) {
+            return tr("Event");
+        } else if (section == 1) {
+            return tr("Status");
+        }
+
+        break;
+    }
+    }
+
+    return QVariant();
+}
+
+bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+    const auto event = static_cast<Pica::DebugContext::Event>(index.row());
+
+    switch (role) {
+    case Role_IsEnabled:
+    {
+        auto context = context_weak.lock();
+        if (!context)
+            return false;
+
+        context->breakpoints[event].enabled = value.toBool();
+        QModelIndex changed_index = createIndex(index.row(), 1);
+        emit dataChanged(changed_index, changed_index);
+        return true;
+    }
+    }
+
+    return false;
+}
+
+
+void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event)
+{
+    auto context = context_weak.lock();
+    if (!context)
+        return;
+
+    active_breakpoint = context->active_breakpoint;
+    at_breakpoint = context->at_breakpoint;
+    emit dataChanged(createIndex(static_cast<int>(event), 0),
+                     createIndex(static_cast<int>(event), 1));
+}
+
+void BreakPointModel::OnResumed()
+{
+    auto context = context_weak.lock();
+    if (!context)
+        return;
+
+    at_breakpoint = context->at_breakpoint;
+    emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
+                     createIndex(static_cast<int>(active_breakpoint), 1));
+    active_breakpoint = context->active_breakpoint;
+}
+
+
+GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context,
+                                                     QWidget* parent)
+    : QDockWidget(tr("Pica Breakpoints"), parent),
+      Pica::DebugContext::BreakPointObserver(debug_context)
+{
+    setObjectName("PicaBreakPointsWidget");
+
+    status_text = new QLabel(tr("Emulation running"));
+    resume_button = new QPushButton(tr("Resume"));
+    resume_button->setEnabled(false);
+
+    breakpoint_model = new BreakPointModel(debug_context, this);
+    breakpoint_list = new QTreeView;
+    breakpoint_list->setModel(breakpoint_model);
+
+    toggle_breakpoint_button = new QPushButton(tr("Enable"));
+    toggle_breakpoint_button->setEnabled(false);
+
+    qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
+
+    connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
+
+    connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
+            this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
+            Qt::BlockingQueuedConnection);
+    connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+    connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
+            breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)),
+            Qt::BlockingQueuedConnection);
+    connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
+
+    connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)),
+            breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)));
+
+    connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+            this, SLOT(OnBreakpointSelectionChanged(QModelIndex)));
+
+    connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled()));
+
+    QWidget* main_widget = new QWidget;
+    auto main_layout = new QVBoxLayout;
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(status_text);
+        sub_layout->addWidget(resume_button);
+        main_layout->addLayout(sub_layout);
+    }
+    main_layout->addWidget(breakpoint_list);
+    main_layout->addWidget(toggle_breakpoint_button);
+    main_widget->setLayout(main_layout);
+
+    setWidget(main_widget);
+}
+
+void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data)
+{
+    // Process in GUI thread
+    emit BreakPointHit(event, data);
+}
+
+void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
+{
+    status_text->setText(tr("Emulation halted at breakpoint"));
+    resume_button->setEnabled(true);
+}
+
+void GraphicsBreakPointsWidget::OnPicaResume()
+{
+    // Process in GUI thread
+    emit Resumed();
+}
+
+void GraphicsBreakPointsWidget::OnResumed()
+{
+    status_text->setText(tr("Emulation running"));
+    resume_button->setEnabled(false);
+}
+
+void GraphicsBreakPointsWidget::OnResumeRequested()
+{
+    if (auto context = context_weak.lock())
+        context->Resume();
+}
+
+void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index)
+{
+    if (!index.isValid()) {
+        toggle_breakpoint_button->setEnabled(false);
+        return;
+    }
+
+    toggle_breakpoint_button->setEnabled(true);
+    UpdateToggleBreakpointButton(index);
+}
+
+void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled()
+{
+    QModelIndex index = breakpoint_list->selectionModel()->currentIndex();
+    bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool());
+
+    breakpoint_model->setData(index, new_state,
+                              BreakPointModel::Role_IsEnabled);
+    UpdateToggleBreakpointButton(index);
+}
+
+void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index)
+{
+    if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) {
+        toggle_breakpoint_button->setText(tr("Disable"));
+    } else {
+        toggle_breakpoint_button->setText(tr("Enable"));
+    }
+}

+ 53 - 0
src/citra_qt/debugger/graphics_breakpoints.hxx

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

+ 38 - 0
src/citra_qt/debugger/graphics_breakpoints_p.hxx

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

+ 255 - 20
src/citra_qt/debugger/graphics_cmdlists.cpp

@@ -1,31 +1,179 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <QLabel>
 #include <QListView>
+#include <QMainWindow>
 #include <QPushButton>
 #include <QVBoxLayout>
 #include <QTreeView>
+#include <QSpinBox>
+#include <QComboBox>
+
+#include "video_core/pica.h"
+#include "video_core/math.h"
+
+#include "video_core/debug_utils/debug_utils.h"
 
 #include "graphics_cmdlists.hxx"
 
-GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent)
-{
+#include "util/spinbox.hxx"
+
+QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) {
+    QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
+    for (int y = 0; y < info.height; ++y) {
+        for (int x = 0; x < info.width; ++x) {
+            Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true);
+            decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
+        }
+    }
+
+    return decoded_image;
+}
+
+class TextureInfoWidget : public QWidget {
+public:
+    TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) {
+        QLabel* image_widget = new QLabel;
+        QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info));
+        image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+        image_widget->setPixmap(image_pixmap);
+
+        QVBoxLayout* layout = new QVBoxLayout;
+        layout->addWidget(image_widget);
+        setLayout(layout);
+    }
+};
+
+TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent)
+    : QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))),
+      info(info) {
+
+    QWidget* main_widget = new QWidget;
+
+    QLabel* image_widget = new QLabel;
+
+    connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&)));
+
+    CSpinBox* phys_address_spinbox = new CSpinBox;
+    phys_address_spinbox->SetBase(16);
+    phys_address_spinbox->SetRange(0, 0xFFFFFFFF);
+    phys_address_spinbox->SetPrefix("0x");
+    phys_address_spinbox->SetValue(info.physical_address);
+    connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64)));
+
+    QComboBox* format_choice = new QComboBox;
+    format_choice->addItem(tr("RGBA8"));
+    format_choice->addItem(tr("RGB8"));
+    format_choice->addItem(tr("RGBA5551"));
+    format_choice->addItem(tr("RGB565"));
+    format_choice->addItem(tr("RGBA4"));
+    format_choice->addItem(tr("IA8"));
+    format_choice->addItem(tr("UNK6"));
+    format_choice->addItem(tr("I8"));
+    format_choice->addItem(tr("A8"));
+    format_choice->addItem(tr("IA4"));
+    format_choice->addItem(tr("UNK10"));
+    format_choice->addItem(tr("A4"));
+    format_choice->setCurrentIndex(static_cast<int>(info.format));
+    connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int)));
+
+    QSpinBox* width_spinbox = new QSpinBox;
+    width_spinbox->setMaximum(65535);
+    width_spinbox->setValue(info.width);
+    connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int)));
+
+    QSpinBox* height_spinbox = new QSpinBox;
+    height_spinbox->setMaximum(65535);
+    height_spinbox->setValue(info.height);
+    connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int)));
+
+    QSpinBox* stride_spinbox = new QSpinBox;
+    stride_spinbox->setMaximum(65535 * 4);
+    stride_spinbox->setValue(info.stride);
+    connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int)));
+
+    QVBoxLayout* main_layout = new QVBoxLayout;
+    main_layout->addWidget(image_widget);
+
+    {
+        QHBoxLayout* sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Source Address:")));
+        sub_layout->addWidget(phys_address_spinbox);
+        main_layout->addLayout(sub_layout);
+    }
+
+    {
+        QHBoxLayout* sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Format")));
+        sub_layout->addWidget(format_choice);
+        main_layout->addLayout(sub_layout);
+    }
+
+    {
+        QHBoxLayout* sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Width:")));
+        sub_layout->addWidget(width_spinbox);
+        sub_layout->addStretch();
+        sub_layout->addWidget(new QLabel(tr("Height:")));
+        sub_layout->addWidget(height_spinbox);
+        sub_layout->addStretch();
+        sub_layout->addWidget(new QLabel(tr("Stride:")));
+        sub_layout->addWidget(stride_spinbox);
+        main_layout->addLayout(sub_layout);
+    }
+
+    main_widget->setLayout(main_layout);
+
+    emit UpdatePixmap(ReloadPixmap());
+
+    setWidget(main_widget);
+}
+
+void TextureInfoDockWidget::OnAddressChanged(qint64 value) {
+    info.physical_address = value;
+    emit UpdatePixmap(ReloadPixmap());
+}
 
+void TextureInfoDockWidget::OnFormatChanged(int value) {
+    info.format = static_cast<Pica::Regs::TextureFormat>(value);
+    emit UpdatePixmap(ReloadPixmap());
 }
 
-int GPUCommandListModel::rowCount(const QModelIndex& parent) const
-{
+void TextureInfoDockWidget::OnWidthChanged(int value) {
+    info.width = value;
+    emit UpdatePixmap(ReloadPixmap());
+}
+
+void TextureInfoDockWidget::OnHeightChanged(int value) {
+    info.height = value;
+    emit UpdatePixmap(ReloadPixmap());
+}
+
+void TextureInfoDockWidget::OnStrideChanged(int value) {
+    info.stride = value;
+    emit UpdatePixmap(ReloadPixmap());
+}
+
+QPixmap TextureInfoDockWidget::ReloadPixmap() const {
+    u8* src = Memory::GetPointer(Pica::PAddrToVAddr(info.physical_address));
+    return QPixmap::fromImage(LoadTexture(src, info));
+}
+
+GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
+
+}
+
+int GPUCommandListModel::rowCount(const QModelIndex& parent) const {
     return pica_trace.writes.size();
 }
 
-int GPUCommandListModel::columnCount(const QModelIndex& parent) const
-{
+int GPUCommandListModel::columnCount(const QModelIndex& parent) const {
     return 2;
 }
 
-QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const
-{
+QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const {
     if (!index.isValid())
         return QVariant();
 
@@ -36,21 +184,39 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const
     if (role == Qt::DisplayRole) {
         QString content;
         if (index.column() == 0) {
-            content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str());
+            QString content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str());
             content.append(" ");
+            return content;
         } else if (index.column() == 1) {
-            content.append(QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0')));
+            QString content = QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0'));
             content.append(QString("%1 ").arg(val, 8, 16, QLatin1Char('0')));
+            return content;
         }
+    } else if (role == CommandIdRole) {
+        return QVariant::fromValue<int>(cmd.cmd_id.Value());
+    }
 
-        return QVariant(content);
+    return QVariant();
+}
+
+QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const {
+    switch(role) {
+    case Qt::DisplayRole:
+    {
+        if (section == 0) {
+            return tr("Command Name");
+        } else if (section == 1) {
+            return tr("Data");
+        }
+
+        break;
+    }
     }
 
     return QVariant();
 }
 
-void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace)
-{
+void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) {
     beginResetModel();
 
     pica_trace = trace;
@@ -58,38 +224,107 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&
     endResetModel();
 }
 
+#define COMMAND_IN_RANGE(cmd_id, reg_name)   \
+    (cmd_id >= PICA_REG_INDEX(reg_name) &&   \
+     cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4)
 
-GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent)
-{
+void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
+    const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt();
+    if (COMMAND_IN_RANGE(command_id, texture0) ||
+        COMMAND_IN_RANGE(command_id, texture1) ||
+        COMMAND_IN_RANGE(command_id, texture2)) {
+
+        unsigned index;
+        if (COMMAND_IN_RANGE(command_id, texture0)) {
+            index = 0;
+        } else if (COMMAND_IN_RANGE(command_id, texture1)) {
+            index = 1;
+        } else {
+            index = 2;
+        }
+        auto config = Pica::registers.GetTextures()[index].config;
+        auto format = Pica::registers.GetTextures()[index].format;
+        auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
+
+        // TODO: Instead, emit a signal here to be caught by the main window widget.
+        auto main_window = static_cast<QMainWindow*>(parent());
+        main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window));
+    }
+}
+
+void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
+    QWidget* new_info_widget;
+
+    const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt();
+    if (COMMAND_IN_RANGE(command_id, texture0) ||
+        COMMAND_IN_RANGE(command_id, texture1) ||
+        COMMAND_IN_RANGE(command_id, texture2)) {
+
+        unsigned index;
+        if (COMMAND_IN_RANGE(command_id, texture0)) {
+            index = 0;
+        } else if (COMMAND_IN_RANGE(command_id, texture1)) {
+            index = 1;
+        } else {
+            index = 2;
+        }
+        auto config = Pica::registers.GetTextures()[index].config;
+        auto format = Pica::registers.GetTextures()[index].format;
+
+        auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
+        u8* src = Memory::GetPointer(Pica::PAddrToVAddr(config.GetPhysicalAddress()));
+        new_info_widget = new TextureInfoWidget(src, info);
+    } else {
+        new_info_widget = new QWidget;
+    }
+
+    widget()->layout()->removeWidget(command_info_widget);
+    delete command_info_widget;
+    widget()->layout()->addWidget(new_info_widget);
+    command_info_widget = new_info_widget;
+}
+#undef COMMAND_IN_RANGE
+
+GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) {
+    setObjectName("Pica Command List");
     GPUCommandListModel* model = new GPUCommandListModel(this);
 
     QWidget* main_widget = new QWidget;
 
-    QTreeView* list_widget = new QTreeView;
+    list_widget = new QTreeView;
     list_widget->setModel(model);
     list_widget->setFont(QFont("monospace"));
     list_widget->setRootIsDecorated(false);
 
-    QPushButton* toggle_tracing = new QPushButton(tr("Start Tracing"));
+    connect(list_widget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)),
+            this, SLOT(SetCommandInfo(const QModelIndex&)));
+    connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)),
+            this, SLOT(OnCommandDoubleClicked(const QModelIndex&)));
+
+    toggle_tracing = new QPushButton(tr("Start Tracing"));
 
     connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing()));
     connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)),
             model, SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&)));
 
+    command_info_widget = new QWidget;
+
     QVBoxLayout* main_layout = new QVBoxLayout;
     main_layout->addWidget(list_widget);
     main_layout->addWidget(toggle_tracing);
+    main_layout->addWidget(command_info_widget);
     main_widget->setLayout(main_layout);
 
     setWidget(main_widget);
 }
 
-void GPUCommandListWidget::OnToggleTracing()
-{
+void GPUCommandListWidget::OnToggleTracing() {
     if (!Pica::DebugUtils::IsPicaTracing()) {
         Pica::DebugUtils::StartPicaTracing();
+        toggle_tracing->setText(tr("Finish Tracing"));
     } else {
         pica_trace = Pica::DebugUtils::FinishPicaTracing();
         emit TracingFinished(*pica_trace);
+        toggle_tracing->setText(tr("Start Tracing"));
     }
 }

+ 38 - 1
src/citra_qt/debugger/graphics_cmdlists.hxx

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -10,16 +10,24 @@
 #include "video_core/gpu_debugger.h"
 #include "video_core/debug_utils/debug_utils.h"
 
+class QPushButton;
+class QTreeView;
+
 class GPUCommandListModel : public QAbstractListModel
 {
     Q_OBJECT
 
 public:
+    enum {
+        CommandIdRole = Qt::UserRole,
+    };
+
     GPUCommandListModel(QObject* parent);
 
     int columnCount(const QModelIndex& parent = QModelIndex()) const override;
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
 
 public slots:
     void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace);
@@ -37,10 +45,39 @@ public:
 
 public slots:
     void OnToggleTracing();
+    void OnCommandDoubleClicked(const QModelIndex&);
+
+    void SetCommandInfo(const QModelIndex&);
 
 signals:
     void TracingFinished(const Pica::DebugUtils::PicaTrace&);
 
 private:
     std::unique_ptr<Pica::DebugUtils::PicaTrace> pica_trace;
+
+    QTreeView* list_widget;
+    QWidget* command_info_widget;
+    QPushButton* toggle_tracing;
+};
+
+class TextureInfoDockWidget : public QDockWidget {
+    Q_OBJECT
+
+public:
+    TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr);
+
+signals:
+    void UpdatePixmap(const QPixmap& pixmap);
+
+private slots:
+    void OnAddressChanged(qint64 value);
+    void OnFormatChanged(int value);
+    void OnWidthChanged(int value);
+    void OnHeightChanged(int value);
+    void OnStrideChanged(int value);
+
+private:
+    QPixmap ReloadPixmap() const;
+
+    Pica::DebugUtils::TextureInfo info;
 };

+ 283 - 0
src/citra_qt/debugger/graphics_framebuffer.cpp

@@ -0,0 +1,283 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QBoxLayout>
+#include <QComboBox>
+#include <QDebug>
+#include <QLabel>
+#include <QMetaType>
+#include <QPushButton>
+#include <QSpinBox>
+
+#include "video_core/pica.h"
+
+#include "graphics_framebuffer.hxx"
+
+#include "util/spinbox.hxx"
+
+BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context,
+                                               const QString& title, QWidget* parent)
+    : QDockWidget(title, parent), BreakPointObserver(debug_context)
+{
+    qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
+
+    connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+    // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
+    //       care of delaying its handling to the GUI thread.
+    connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)),
+            this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)),
+            Qt::BlockingQueuedConnection);
+}
+
+void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data)
+{
+    emit BreakPointHit(event, data);
+}
+
+void BreakPointObserverDock::OnPicaResume()
+{
+    emit Resumed();
+}
+
+
+GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context,
+                                                     QWidget* parent)
+    : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent),
+      framebuffer_source(Source::PicaTarget)
+{
+    setObjectName("PicaFramebuffer");
+
+    framebuffer_source_list = new QComboBox;
+    framebuffer_source_list->addItem(tr("Active Render Target"));
+    framebuffer_source_list->addItem(tr("Custom"));
+    framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source));
+
+    framebuffer_address_control = new CSpinBox;
+    framebuffer_address_control->SetBase(16);
+    framebuffer_address_control->SetRange(0, 0xFFFFFFFF);
+    framebuffer_address_control->SetPrefix("0x");
+
+    framebuffer_width_control = new QSpinBox;
+    framebuffer_width_control->setMinimum(1);
+    framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
+
+    framebuffer_height_control = new QSpinBox;
+    framebuffer_height_control->setMinimum(1);
+    framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
+
+    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("RGB565"));
+    framebuffer_format_control->addItem(tr("RGBA4"));
+
+    // TODO: This QLabel should shrink the image to the available space rather than just expanding...
+    framebuffer_picture_label = new QLabel;
+
+    auto enlarge_button = new QPushButton(tr("Enlarge"));
+
+    // Connections
+    connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
+    connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int)));
+    connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64)));
+    connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int)));
+    connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int)));
+    connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int)));
+
+    auto main_widget = new QWidget;
+    auto main_layout = new QVBoxLayout;
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Source:")));
+        sub_layout->addWidget(framebuffer_source_list);
+        main_layout->addLayout(sub_layout);
+    }
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Virtual Address:")));
+        sub_layout->addWidget(framebuffer_address_control);
+        main_layout->addLayout(sub_layout);
+    }
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Width:")));
+        sub_layout->addWidget(framebuffer_width_control);
+        main_layout->addLayout(sub_layout);
+    }
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Height:")));
+        sub_layout->addWidget(framebuffer_height_control);
+        main_layout->addLayout(sub_layout);
+    }
+    {
+        auto sub_layout = new QHBoxLayout;
+        sub_layout->addWidget(new QLabel(tr("Format:")));
+        sub_layout->addWidget(framebuffer_format_control);
+        main_layout->addLayout(sub_layout);
+    }
+    main_layout->addWidget(framebuffer_picture_label);
+    main_layout->addWidget(enlarge_button);
+    main_widget->setLayout(main_layout);
+    setWidget(main_widget);
+
+    // Load current data - TODO: Make sure this works when emulation is not running
+    if (debug_context && debug_context->at_breakpoint)
+        emit Update();
+    widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint
+}
+
+void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
+{
+    emit Update();
+    widget()->setEnabled(true);
+}
+
+void GraphicsFramebufferWidget::OnResumed()
+{
+    widget()->setEnabled(false);
+}
+
+void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value)
+{
+    framebuffer_source = static_cast<Source>(new_value);
+    emit Update();
+}
+
+void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value)
+{
+    if (framebuffer_address != new_value) {
+        framebuffer_address = static_cast<unsigned>(new_value);
+
+        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+        emit Update();
+    }
+}
+
+void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value)
+{
+    if (framebuffer_width != new_value) {
+        framebuffer_width = new_value;
+
+        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+        emit Update();
+    }
+}
+
+void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
+{
+    if (framebuffer_height != new_value) {
+        framebuffer_height = new_value;
+
+        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+        emit Update();
+    }
+}
+
+void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value)
+{
+    if (framebuffer_format != static_cast<Format>(new_value)) {
+        framebuffer_format = static_cast<Format>(new_value);
+
+        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+        emit Update();
+    }
+}
+
+void GraphicsFramebufferWidget::OnUpdate()
+{
+    QPixmap pixmap;
+
+    switch (framebuffer_source) {
+    case Source::PicaTarget:
+    {
+        // TODO: Store a reference to the registers in the debug context instead of accessing them directly...
+
+        auto framebuffer = Pica::registers.framebuffer;
+        using Framebuffer = decltype(framebuffer);
+
+        framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
+        framebuffer_width = framebuffer.GetWidth();
+        framebuffer_height = framebuffer.GetHeight();
+        framebuffer_format = static_cast<Format>(framebuffer.color_format);
+
+        break;
+    }
+
+    case Source::Custom:
+    {
+        // Keep user-specified values
+        break;
+    }
+
+    default:
+        qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source);
+        break;
+    }
+
+    // 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 y = 0; y < framebuffer_height; ++y) {
+            for (unsigned 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*/));
+            }
+        }
+        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 y = 0; y < framebuffer_height; ++y) {
+            for (unsigned 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*/));
+            }
+        }
+        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 y = 0; y < framebuffer_height; ++y) {
+            for (unsigned x = 0; x < framebuffer_width; ++x) {
+                u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2);
+                u8 r = (value >> 11) & 0x1F;
+                u8 g = (value >> 6) & 0x1F;
+                u8 b = (value >> 1) & 0x1F;
+                u8 a = 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;
+    }
+
+    framebuffer_address_control->SetValue(framebuffer_address);
+    framebuffer_width_control->setValue(framebuffer_width);
+    framebuffer_height_control->setValue(framebuffer_height);
+    framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format));
+    framebuffer_picture_label->setPixmap(pixmap);
+}

+ 92 - 0
src/citra_qt/debugger/graphics_framebuffer.hxx

@@ -0,0 +1,92 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDockWidget>
+
+#include "video_core/debug_utils/debug_utils.h"
+
+class QComboBox;
+class QLabel;
+class QSpinBox;
+
+class CSpinBox;
+
+// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots.
+// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while
+// the widget usually wants to perform reactions in the GUI thread.
+class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver {
+    Q_OBJECT
+
+public:
+    BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title,
+                           QWidget* parent = nullptr);
+
+    void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override;
+    void OnPicaResume() override;
+
+private slots:
+    virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0;
+    virtual void OnResumed() = 0;
+
+signals:
+    void Resumed();
+    void BreakPointHit(Pica::DebugContext::Event event, void* data);
+};
+
+class GraphicsFramebufferWidget : public BreakPointObserverDock {
+    Q_OBJECT
+
+    using Event = Pica::DebugContext::Event;
+
+    enum class Source {
+        PicaTarget = 0,
+        Custom = 1,
+
+        // TODO: Add GPU framebuffer sources!
+    };
+
+    enum class Format {
+        RGBA8    = 0,
+        RGB8     = 1,
+        RGBA5551 = 2,
+        RGB565   = 3,
+        RGBA4    = 4,
+    };
+
+public:
+    GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
+
+public slots:
+    void OnFramebufferSourceChanged(int new_value);
+    void OnFramebufferAddressChanged(qint64 new_value);
+    void OnFramebufferWidthChanged(int new_value);
+    void OnFramebufferHeightChanged(int new_value);
+    void OnFramebufferFormatChanged(int new_value);
+    void OnUpdate();
+
+private slots:
+    void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
+    void OnResumed() override;
+
+signals:
+    void Update();
+
+private:
+
+    QComboBox* framebuffer_source_list;
+    CSpinBox* framebuffer_address_control;
+    QSpinBox* framebuffer_width_control;
+    QSpinBox* framebuffer_height_control;
+    QComboBox* framebuffer_format_control;
+
+    QLabel* framebuffer_picture_label;
+
+    Source framebuffer_source;
+    unsigned framebuffer_address;
+    unsigned framebuffer_width;
+    unsigned framebuffer_height;
+    Format framebuffer_format;
+};

+ 2 - 2
src/citra_qt/hotkeys.cpp

@@ -5,7 +5,7 @@
 
 struct Hotkey
 {
-    Hotkey() : shortcut(NULL), context(Qt::WindowShortcut) {}
+    Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
 
     QKeySequence keyseq;
     QShortcut* shortcut;
@@ -81,7 +81,7 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge
     Hotkey& hk = hotkey_groups[group][action];
 
     if (!hk.shortcut)
-        hk.shortcut = new QShortcut(hk.keyseq, widget, NULL, NULL, hk.context);
+        hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context);
 
     return hk.shortcut;
 }

+ 45 - 19
src/citra_qt/main.cpp

@@ -1,3 +1,5 @@
+#include <thread>
+
 #include <QtGui>
 #include <QDesktopWidget>
 #include <QFileDialog>
@@ -5,8 +7,13 @@
 #include "main.hxx"
 
 #include "common/common.h"
+#include "common/logging/text_formatter.h"
+#include "common/logging/log.h"
+#include "common/logging/backend.h"
+#include "common/logging/filter.h"
 #include "common/platform.h"
-#include "common/log_manager.h"
+#include "common/scope_exit.h"
+
 #if EMU_PLATFORM == PLATFORM_LINUX
 #include <unistd.h>
 #endif
@@ -20,7 +27,9 @@
 #include "debugger/callstack.hxx"
 #include "debugger/ramview.hxx"
 #include "debugger/graphics.hxx"
+#include "debugger/graphics_breakpoints.hxx"
 #include "debugger/graphics_cmdlists.hxx"
+#include "debugger/graphics_framebuffer.hxx"
 
 #include "core/settings.h"
 #include "core/system.h"
@@ -31,16 +40,12 @@
 
 #include "version.h"
 
-
 GMainWindow::GMainWindow()
 {
-    LogManager::Init();
+    Pica::g_debug_context = Pica::DebugContext::Construct();
 
     Config config;
 
-    if (!Settings::values.enable_log)
-        LogManager::Shutdown();
-
     ui.setupUi(this);
     statusBar()->hide();
 
@@ -67,12 +72,22 @@ GMainWindow::GMainWindow()
     addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
     graphicsCommandsWidget->hide();
 
+    auto graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this);
+    addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
+    graphicsBreakpointsWidget->hide();
+
+    auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this);
+    addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget);
+    graphicsFramebufferWidget->hide();
+
     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
     debug_menu->addAction(disasmWidget->toggleViewAction());
     debug_menu->addAction(registersWidget->toggleViewAction());
     debug_menu->addAction(callstackWidget->toggleViewAction());
     debug_menu->addAction(graphicsWidget->toggleViewAction());
     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
+    debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
+    debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction());
 
     // Set default UI state
     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
@@ -131,24 +146,20 @@ GMainWindow::GMainWindow()
 GMainWindow::~GMainWindow()
 {
     // will get automatically deleted otherwise
-    if (render_window->parent() == NULL)
+    if (render_window->parent() == nullptr)
         delete render_window;
+
+    Pica::g_debug_context.reset();
 }
 
 void GMainWindow::BootGame(std::string filename)
 {
-    NOTICE_LOG(MASTER_LOG, "Citra starting...\n");
+    LOG_INFO(Frontend, "Citra starting...\n");
     System::Init(render_window);
 
-    if (Core::Init()) {
-        ERROR_LOG(MASTER_LOG, "Core initialization failed, exiting...");
-        Core::Stop();
-        exit(1);
-    }
-
     // Load a game or die...
     if (Loader::ResultStatus::Success != Loader::LoadFile(filename)) {
-        ERROR_LOG(BOOT, "Failed to load ROM!");
+        LOG_CRITICAL(Frontend, "Failed to load ROM!");
     }
 
     disasmWidget->Init();
@@ -164,7 +175,7 @@ void GMainWindow::BootGame(std::string filename)
 
 void GMainWindow::OnMenuLoadFile()
 {
-    QString filename = QFileDialog::getOpenFileName(this, tr("Load file"), QString(), tr("3DS executable (*.elf *.axf *.bin *.cci *.cxi)"));
+    QString filename = QFileDialog::getOpenFileName(this, tr("Load file"), QString(), tr("3DS executable (*.3dsx *.elf *.axf *.bin *.cci *.cxi)"));
     if (filename.size())
        BootGame(filename.toLatin1().data());
 }
@@ -213,18 +224,21 @@ void GMainWindow::OnOpenHotkeysDialog()
 void GMainWindow::ToggleWindowMode()
 {
     bool enable = ui.action_Popout_Window_Mode->isChecked();
-    if (enable && render_window->parent() != NULL)
+    if (enable && render_window->parent() != nullptr)
     {
         ui.horizontalLayout->removeWidget(render_window);
-        render_window->setParent(NULL);
+        render_window->setParent(nullptr);
         render_window->setVisible(true);
         render_window->RestoreGeometry();
+        render_window->setFocusPolicy(Qt::NoFocus);
     }
-    else if (!enable && render_window->parent() == NULL)
+    else if (!enable && render_window->parent() == nullptr)
     {
         render_window->BackupGeometry();
         ui.horizontalLayout->addWidget(render_window);
         render_window->setVisible(true);
+        render_window->setFocusPolicy(Qt::ClickFocus);
+        render_window->setFocus();
     }
 }
 
@@ -255,9 +269,21 @@ void GMainWindow::closeEvent(QCloseEvent* event)
 
 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);
+    SCOPE_EXIT({
+        logger->Close();
+        logging_thread.join();
+    });
+
     QApplication::setAttribute(Qt::AA_X11InitThreads);
     QApplication app(argc, argv);
+
     GMainWindow main_window;
+    // After settings have been loaded by GMainWindow, apply the filter
+    log_filter.ParseFilterString(Settings::values.log_filter);
+
     main_window.show();
     return app.exec();
 }

+ 303 - 0
src/citra_qt/util/spinbox.cpp

@@ -0,0 +1,303 @@
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+
+// Copyright 2014 Tony Wasserka
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//     * Neither the name of the owner nor the names of its contributors may
+//       be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <QLineEdit>
+#include <QRegExpValidator>
+
+#include "common/log.h"
+
+#include "spinbox.hxx"
+
+CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0)
+{
+    // TODO: Might be nice to not immediately call the slot.
+    //       Think of an address that is being replaced by a different one, in which case a lot
+    //       invalid intermediate addresses would be read from during editing.
+    connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished()));
+
+    UpdateText();
+}
+
+void CSpinBox::SetValue(qint64 val)
+{
+    auto old_value = value;
+    value = std::max(std::min(val, max_value), min_value);
+
+    if (old_value != value) {
+        UpdateText();
+        emit ValueChanged(value);
+    }
+}
+
+void CSpinBox::SetRange(qint64 min, qint64 max)
+{
+    min_value = min;
+    max_value = max;
+
+    SetValue(value);
+    UpdateText();
+}
+
+void CSpinBox::stepBy(int steps)
+{
+    auto new_value = value;
+    // Scale number of steps by the currently selected digit
+    // TODO: Move this code elsewhere and enable it.
+    // TODO: Support for num_digits==0, too
+    // TODO: Support base!=16, too
+    // TODO: Make the cursor not jump back to the end of the line...
+    /*if (base == 16 && num_digits > 0) {
+        int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
+        digit = std::max(0, std::min(digit, num_digits - 1));
+        steps <<= digit * 4;
+    }*/
+
+    // Increment "new_value" by "steps", and perform annoying overflow checks, too.
+    if (steps < 0 && new_value + steps > new_value) {
+        new_value = std::numeric_limits<qint64>::min();
+    } else if (steps > 0 && new_value + steps < new_value) {
+        new_value = std::numeric_limits<qint64>::max();
+    } else {
+        new_value += steps;
+    }
+
+    SetValue(new_value);
+    UpdateText();
+}
+
+QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
+{
+    StepEnabled ret = StepNone;
+
+    if (value > min_value)
+        ret |= StepDownEnabled;
+
+    if (value < max_value)
+        ret |= StepUpEnabled;
+
+    return ret;
+}
+
+void CSpinBox::SetBase(int base)
+{
+    this->base = base;
+
+    UpdateText();
+}
+
+void CSpinBox::SetNumDigits(int num_digits)
+{
+    this->num_digits = num_digits;
+
+    UpdateText();
+}
+
+void CSpinBox::SetPrefix(const QString& prefix)
+{
+    this->prefix = prefix;
+
+    UpdateText();
+}
+
+void CSpinBox::SetSuffix(const QString& suffix)
+{
+    this->suffix = suffix;
+
+    UpdateText();
+}
+
+static QString StringToInputMask(const QString& input) {
+    QString mask = input;
+
+    // ... replace any special characters by their escaped counterparts ...
+    mask.replace("\\", "\\\\");
+    mask.replace("A", "\\A");
+    mask.replace("a", "\\a");
+    mask.replace("N", "\\N");
+    mask.replace("n", "\\n");
+    mask.replace("X", "\\X");
+    mask.replace("x", "\\x");
+    mask.replace("9", "\\9");
+    mask.replace("0", "\\0");
+    mask.replace("D", "\\D");
+    mask.replace("d", "\\d");
+    mask.replace("#", "\\#");
+    mask.replace("H", "\\H");
+    mask.replace("h", "\\h");
+    mask.replace("B", "\\B");
+    mask.replace("b", "\\b");
+    mask.replace(">", "\\>");
+    mask.replace("<", "\\<");
+    mask.replace("!", "\\!");
+
+    return mask;
+}
+
+void CSpinBox::UpdateText()
+{
+    // If a fixed number of digits is used, we put the line edit in insertion mode by setting an
+    // input mask.
+    QString mask;
+    if (num_digits != 0) {
+        mask += StringToInputMask(prefix);
+
+        // For base 10 and negative range, demand a single sign character
+        if (HasSign())
+            mask += "X"; // identified as "-" or "+" in the validator
+
+        // Uppercase digits greater than 9.
+        mask += ">";
+
+        // The greatest signed 64-bit number has 19 decimal digits.
+        // TODO: Could probably make this more generic with some logarithms.
+        // For reference, unsigned 64-bit can have up to 20 decimal digits.
+        int digits = (num_digits != 0) ? num_digits
+                     : (base == 16) ? 16
+                     : (base == 10) ? 19
+                     : 0xFF; // fallback case...
+
+        // Match num_digits digits
+        // Digits irrelevant to the chosen number base are filtered in the validator
+        mask += QString("H").repeated(std::max(num_digits, 1));
+
+        // Switch off case conversion
+        mask += "!";
+
+        mask += StringToInputMask(suffix);
+    }
+    lineEdit()->setInputMask(mask);
+
+    // Set new text without changing the cursor position. This will cause the cursor to briefly
+    // appear at the end of the line and then to jump back to its original position. That's
+    // a bit ugly, but better than having setText() move the cursor permanently all the time.
+    int cursor_position = lineEdit()->cursorPosition();
+    lineEdit()->setText(TextFromValue());
+    lineEdit()->setCursorPosition(cursor_position);
+}
+
+QString CSpinBox::TextFromValue()
+{
+    return prefix
+           + QString(HasSign() ? ((value < 0) ? "-" : "+") : "")
+           + QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper()
+           + suffix;
+}
+
+qint64 CSpinBox::ValueFromText()
+{
+    unsigned strpos = prefix.length();
+
+    QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
+    return num_string.toLongLong(nullptr, base);
+}
+
+bool CSpinBox::HasSign() const
+{
+    return base == 10 && min_value < 0;
+}
+
+void CSpinBox::OnEditingFinished()
+{
+    // Only update for valid input
+    QString input = lineEdit()->text();
+    int pos = 0;
+    if (QValidator::Acceptable == validate(input, pos))
+        SetValue(ValueFromText());
+}
+
+QValidator::State CSpinBox::validate(QString& input, int& pos) const
+{
+    if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
+        return QValidator::Invalid;
+
+    int strpos = prefix.length();
+
+    // Empty "numbers" allowed as intermediate values
+    if (strpos >= input.length() - HasSign() - suffix.length())
+        return QValidator::Intermediate;
+
+    _dbg_assert_(Frontend, base <= 10 || base == 16);
+    QString regexp;
+
+    // Demand sign character for negative ranges
+    if (HasSign())
+        regexp += "[+\\-]";
+
+    // Match digits corresponding to the chosen number base.
+    regexp += QString("[0-%1").arg(std::min(base, 9));
+    if (base == 16) {
+        regexp += "a-fA-F";
+    }
+    regexp += "]";
+
+    // Specify number of digits
+    if (num_digits > 0) {
+        regexp += QString("{%1}").arg(num_digits);
+    } else {
+        regexp += "+";
+    }
+
+    // Match string
+    QRegExp num_regexp(regexp);
+    int num_pos = strpos;
+    QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
+
+    if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
+        return QValidator::Invalid;
+
+    sub_input = sub_input.left(num_regexp.matchedLength());
+    bool ok;
+    qint64 val = sub_input.toLongLong(&ok, base);
+
+    if (!ok)
+        return QValidator::Invalid;
+
+    // Outside boundaries => don't accept
+    if (val < min_value || val > max_value)
+        return QValidator::Invalid;
+
+    // Make sure we are actually at the end of this string...
+    strpos += num_regexp.matchedLength();
+
+    if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
+        return QValidator::Invalid;
+    } else {
+        strpos += suffix.length();
+    }
+
+    if (strpos != input.length())
+        return QValidator::Invalid;
+
+    // At this point we can say for sure that the input is fine. Let's fix it up a bit though
+    input.replace(num_pos, sub_input.length(), sub_input.toUpper());
+
+    return QValidator::Acceptable;
+}

+ 88 - 0
src/citra_qt/util/spinbox.hxx

@@ -0,0 +1,88 @@
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+
+// Copyright 2014 Tony Wasserka
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//     * Neither the name of the owner nor the names of its contributors may
+//       be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#pragma once
+
+#include <QAbstractSpinBox>
+#include <QtGlobal>
+
+class QVariant;
+
+/**
+ * A custom spin box widget with enhanced functionality over Qt's QSpinBox
+ */
+class CSpinBox : public QAbstractSpinBox {
+    Q_OBJECT
+
+public:
+    CSpinBox(QWidget* parent = nullptr);
+
+    void stepBy(int steps) override;
+    StepEnabled stepEnabled() const override;
+
+    void SetValue(qint64 val);
+
+    void SetRange(qint64 min, qint64 max);
+
+    void SetBase(int base);
+
+    void SetPrefix(const QString& prefix);
+    void SetSuffix(const QString& suffix);
+
+    void SetNumDigits(int num_digits);
+
+    QValidator::State validate(QString& input, int& pos) const override;
+
+signals:
+    void ValueChanged(qint64 val);
+
+private slots:
+    void OnEditingFinished();
+
+private:
+    void UpdateText();
+
+    bool HasSign() const;
+
+    QString TextFromValue();
+    qint64 ValueFromText();
+
+    qint64 min_value, max_value;
+
+    qint64 value;
+
+    QString prefix, suffix;
+
+    int base;
+
+    int num_digits;
+};

+ 10 - 4
src/common/CMakeLists.txt

@@ -3,14 +3,15 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
 
 set(SRCS
             break_points.cpp
-            console_listener.cpp
             emu_window.cpp
             extended_trace.cpp
             file_search.cpp
             file_util.cpp
             hash.cpp
             key_map.cpp
-            log_manager.cpp
+            logging/filter.cpp
+            logging/text_formatter.cpp
+            logging/backend.cpp
             math_util.cpp
             mem_arena.cpp
             memory_util.cpp
@@ -32,7 +33,7 @@ set(HEADERS
             common_funcs.h
             common_paths.h
             common_types.h
-            console_listener.h
+            concurrent_ring_buffer.h
             cpu_detect.h
             debug_interface.h
             emu_window.h
@@ -44,13 +45,18 @@ set(HEADERS
             key_map.h
             linear_disk_cache.h
             log.h
-            log_manager.h
+            logging/text_formatter.h
+            logging/filter.h
+            logging/log.h
+            logging/backend.h
+            make_unique.h
             math_util.h
             mem_arena.h
             memory_util.h
             msg_handler.h
             platform.h
             scm_rev.h
+            scope_exit.h
             string_util.h
             swap.h
             symbols.h

+ 12 - 2
src/common/bit_field.h

@@ -1,4 +1,4 @@
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 
@@ -142,7 +142,7 @@ public:
 
     __forceinline BitField& operator=(T val)
     {
-        storage = (storage & ~GetMask()) | (((StorageType)val << position) & GetMask());
+        Assign(val);
         return *this;
     }
 
@@ -151,6 +151,10 @@ public:
         return Value();
     }
 
+    __forceinline void Assign(const T& value) {
+        storage = (storage & ~GetMask()) | (((StorageType)value << position) & GetMask());
+    }
+
     __forceinline T Value() const
     {
         if (std::numeric_limits<T>::is_signed)
@@ -164,6 +168,12 @@ public:
         }
     }
 
+    // TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015
+    __forceinline bool ToBool() const
+    {
+        return Value() != 0;
+    }
+
 private:
     // StorageType is T for non-enum types and the underlying type of T if
     // T is an enumeration. Note that T is wrapped within an enable_if in the

+ 3 - 3
src/common/break_points.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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"
@@ -180,7 +180,7 @@ void TMemCheck::Action(DebugInterface *debug_interface, u32 iValue, u32 addr,
     {
         if (Log)
         {
-            INFO_LOG(MEMMAP, "CHK %08x (%s) %s%i %0*x at %08x (%s)",
+            LOG_DEBUG(Debug_Breakpoint, "CHK %08x (%s) %s%i %0*x at %08x (%s)",
                 pc, debug_interface->getDescription(pc).c_str(),
                 write ? "Write" : "Read", size*8, size*2, iValue, addr,
                 debug_interface->getDescription(addr).c_str()

+ 2 - 2
src/common/break_points.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 41 - 18
src/common/chunk_file.h

@@ -154,7 +154,7 @@ public:
             Do(foundVersion);
 
         if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
-            WARN_LOG(COMMON, "Savestate failure: wrong version %d found for %s", foundVersion, title);
+            LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion, title);
             SetError(ERROR_FAILURE);
             return PointerWrapSection(*this, -1, title);
         }
@@ -178,7 +178,14 @@ public:
         case MODE_READ:    if (memcmp(data, *ptr, size) != 0) return false; break;
         case MODE_WRITE: memcpy(*ptr, data, size); break;
         case MODE_MEASURE: break;  // MODE_MEASURE - don't need to do anything
-        case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break;
+        case MODE_VERIFY:
+            for (int i = 0; i < size; i++) {
+                _dbg_assert_msg_(Common, ((u8*)data)[i] == (*ptr)[i],
+                    "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
+                    ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i],
+                    (*ptr)[i], (*ptr)[i], &(*ptr)[i]);
+            }
+            break;
         default: break;  // throw an error?
         }
         (*ptr) += size;
@@ -191,7 +198,14 @@ public:
         case MODE_READ:    memcpy(data, *ptr, size); break;
         case MODE_WRITE: memcpy(*ptr, data, size); break;
         case MODE_MEASURE: break;  // MODE_MEASURE - don't need to do anything
-        case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break;
+        case MODE_VERIFY:
+            for (int i = 0; i < size; i++) {
+                _dbg_assert_msg_(Common, ((u8*)data)[i] == (*ptr)[i],
+                    "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
+                    ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i],
+                    (*ptr)[i], (*ptr)[i], &(*ptr)[i]);
+            }
+            break;
         default: break;  // throw an error?
         }
         (*ptr) += size;
@@ -204,11 +218,11 @@ public:
         {
             for (auto it = x.begin(), end = x.end(); it != end; ++it)
             {
-                if (it->second != NULL)
+                if (it->second != nullptr)
                     delete it->second;
             }
         }
-        T *dv = NULL;
+        T *dv = nullptr;
         DoMap(x, dv);
     }
 
@@ -264,11 +278,11 @@ public:
         {
             for (auto it = x.begin(), end = x.end(); it != end; ++it)
             {
-                if (it->second != NULL)
+                if (it->second != nullptr)
                     delete it->second;
             }
         }
-        T *dv = NULL;
+        T *dv = nullptr;
         DoMultimap(x, dv);
     }
 
@@ -320,7 +334,7 @@ public:
     template<class T>
     void Do(std::vector<T *> &x)
     {
-        T *dv = NULL;
+        T *dv = nullptr;
         DoVector(x, dv);
     }
 
@@ -369,7 +383,7 @@ public:
     template<class T>
     void Do(std::deque<T *> &x)
     {
-        T *dv = NULL;
+        T *dv = nullptr;
         DoDeque(x, dv);
     }
 
@@ -395,7 +409,7 @@ public:
     template<class T>
     void Do(std::list<T *> &x)
     {
-        T *dv = NULL;
+        T *dv = nullptr;
         Do(x, dv);
     }
 
@@ -433,7 +447,7 @@ public:
         {
             for (auto it = x.begin(), end = x.end(); it != end; ++it)
             {
-                if (*it != NULL)
+                if (*it != nullptr)
                     delete *it;
             }
         }
@@ -476,7 +490,7 @@ public:
             break;
 
         default:
-            ERROR_LOG(COMMON, "Savestate error: invalid mode %d.", mode);
+            LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
         }
     }
 
@@ -490,7 +504,12 @@ public:
         case MODE_READ:        x = (char*)*ptr; break;
         case MODE_WRITE:    memcpy(*ptr, x.c_str(), stringLen); break;
         case MODE_MEASURE: break;
-        case MODE_VERIFY: _dbg_assert_msg_(COMMON, !strcmp(x.c_str(), (char*)*ptr), "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", x.c_str(), (char*)*ptr, ptr); break;
+        case MODE_VERIFY:
+            _dbg_assert_msg_(Common,
+                !strcmp(x.c_str(), (char*)*ptr),
+                "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
+                x.c_str(), (char*)*ptr, ptr);
+            break;
         }
         (*ptr) += stringLen;
     }
@@ -504,7 +523,11 @@ public:
         case MODE_READ:        x = (wchar_t*)*ptr; break;
         case MODE_WRITE:    memcpy(*ptr, x.c_str(), stringLen); break;
         case MODE_MEASURE: break;
-        case MODE_VERIFY: _dbg_assert_msg_(COMMON, x == (wchar_t*)*ptr, "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", x.c_str(), (wchar_t*)*ptr, ptr); break;
+        case MODE_VERIFY:
+            _dbg_assert_msg_(Common, x == (wchar_t*)*ptr,
+                "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
+                x.c_str(), (wchar_t*)*ptr, ptr);
+            break;
         }
         (*ptr) += stringLen;
     }
@@ -518,7 +541,7 @@ public:
     void DoClass(T *&x) {
         if (mode == MODE_READ)
         {
-            if (x != NULL)
+            if (x != nullptr)
                 delete x;
             x = new T();
         }
@@ -567,7 +590,7 @@ public:
                 {
                     if (mode == MODE_READ)
                     {
-                        cur->next = 0;
+                        cur->next = nullptr;
                         list_cur = cur;
                         if (prev)
                             prev->next = cur;
@@ -586,13 +609,13 @@ public:
                 if (mode == MODE_READ)
                 {
                     if (prev)
-                        prev->next = 0;
+                        prev->next = nullptr;
                     if (list_end)
                         *list_end = prev;
                     if (list_cur)
                     {
                         if (list_start == list_cur)
-                            list_start = 0;
+                            list_start = nullptr;
                         do
                         {
                             LinkedListItem<T>* next = list_cur->next;

+ 2 - 2
src/common/common.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 8 - 3
src/common/common_funcs.h

@@ -1,9 +1,12 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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 "common_types.h"
+#include <cstdlib>
+
 #ifdef _WIN32
 #define SLEEP(x) Sleep(x)
 #else
@@ -73,6 +76,8 @@ inline u64 _rotr64(u64 x, unsigned int shift){
 }
 
 #else // _MSC_VER
+#include <locale.h>
+
 // Function Cross-Compatibility
     #define strcasecmp _stricmp
     #define strncasecmp _strnicmp
@@ -106,7 +111,7 @@ inline u64 _rotr64(u64 x, unsigned int shift){
             // Restore the global locale
             _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
         }
-        else if(new_locale != NULL)
+        else if(new_locale != nullptr)
         {
             // Configure the thread to set the locale only for this thread
             _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);

+ 8 - 15
src/common/common_paths.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
@@ -29,19 +29,6 @@
     #endif
 #endif
 
-// Shared data dirs (Sys and shared User for linux)
-#ifdef _WIN32
-    #define SYSDATA_DIR "sys"
-#else
-    #ifdef DATA_DIR
-        #define SYSDATA_DIR DATA_DIR "sys"
-        #define SHARED_USER_DIR  DATA_DIR USERDATA_DIR DIR_SEP
-    #else
-        #define SYSDATA_DIR "sys"
-        #define SHARED_USER_DIR  ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP
-    #endif
-#endif
-
 // Dirs in both User and Sys
 #define EUR_DIR "EUR"
 #define USA_DIR "USA"
@@ -53,6 +40,9 @@
 #define MAPS_DIR          "maps"
 #define CACHE_DIR         "cache"
 #define SDMC_DIR          "sdmc"
+#define SAVEDATA_DIR      "savedata"
+#define SYSDATA_DIR       "sysdata"
+#define SYSSAVEDATA_DIR   "syssavedata"
 #define SHADERCACHE_DIR   "shader_cache"
 #define STATESAVES_DIR    "state_saves"
 #define SCREENSHOTS_DIR   "screenShots"
@@ -70,6 +60,9 @@
 #define DEBUGGER_CONFIG   "debugger.ini"
 #define LOGGER_CONFIG     "logger.ini"
 
+// Sys files
+#define SHARED_FONT       "shared_font.bin"
+
 // Files in the directory returned by GetUserPath(D_LOGS_IDX)
 #define MAIN_LOG "emu.log"
 

+ 0 - 2
src/common/common_types.h

@@ -41,8 +41,6 @@ typedef std::int64_t s64; ///< 64-bit signed int
 typedef float   f32; ///< 32-bit floating point
 typedef double  f64; ///< 64-bit floating point
 
-#include "common/common.h"
-
 /// Union for fast 16-bit type casting
 union t16 {
     u8  _u8[2];             ///< 8-bit unsigned char(s)

+ 164 - 0
src/common/concurrent_ring_buffer.h

@@ -0,0 +1,164 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <condition_variable>
+#include <cstdint>
+#include <mutex>
+#include <thread>
+
+#include "common/common.h" // for NonCopyable
+#include "common/log.h" // for _dbg_assert_
+
+namespace Common {
+
+/**
+ * A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits
+ * multiple threads to push and pop from a queue of bounded size.
+ */
+template <typename T, size_t ArraySize>
+class ConcurrentRingBuffer : private NonCopyable {
+public:
+    /// Value returned by the popping functions when the queue has been closed.
+    static const size_t QUEUE_CLOSED = -1;
+
+    ConcurrentRingBuffer() {}
+
+    ~ConcurrentRingBuffer() {
+        // If for whatever reason the queue wasn't completely drained, destroy the left over items.
+        for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) {
+            Data()[i].~T();
+        }
+    }
+
+    /**
+     * Pushes a value to the queue. If the queue is full, this method will block. Does nothing if
+     * the queue is closed.
+     */
+    void Push(T val) {
+        std::unique_lock<std::mutex> lock(mutex);
+        if (closed) {
+            return;
+        }
+
+        // If the buffer is full, wait
+        writer.wait(lock, [&]{
+            return (writer_index + 1) % ArraySize != reader_index;
+        });
+
+        T* item = &Data()[writer_index];
+        new (item) T(std::move(val));
+
+        writer_index = (writer_index + 1) % ArraySize;
+
+        // Wake up waiting readers
+        lock.unlock();
+        reader.notify_one();
+    }
+
+    /**
+     * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not
+     * block, and might return 0 values if there are no elements in the queue when it is called.
+     *
+     * @return The number of elements stored in `dest`. If the queue has been closed, returns
+     *          `QUEUE_CLOSED`.
+     */
+    size_t Pop(T* dest, size_t dest_len) {
+        std::unique_lock<std::mutex> lock(mutex);
+        if (closed && !CanRead()) {
+            return QUEUE_CLOSED;
+        }
+        return PopInternal(dest, dest_len);
+    }
+
+    /**
+     * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block
+     * if there are no elements in the queue when it is called.
+     *
+     * @return The number of elements stored in `dest`. If the queue has been closed, returns
+     *         `QUEUE_CLOSED`.
+     */
+    size_t BlockingPop(T* dest, size_t dest_len) {
+        std::unique_lock<std::mutex> lock(mutex);
+        if (closed && !CanRead()) {
+            return QUEUE_CLOSED;
+        }
+
+        while (!CanRead()) {
+            reader.wait(lock);
+            if (closed && !CanRead()) {
+                return QUEUE_CLOSED;
+            }
+        }
+        _dbg_assert_(Common, CanRead());
+        return PopInternal(dest, dest_len);
+    }
+
+    /**
+     * Closes the queue. After calling this method, `Push` operations won't have any effect, and
+     * `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow
+     * a graceful shutdown of all consumers.
+     */
+    void Close() {
+        std::unique_lock<std::mutex> lock(mutex);
+        closed = true;
+        // We need to wake up any reader that are waiting for an item that will never come.
+        lock.unlock();
+        reader.notify_all();
+    }
+
+    /// Returns true if `Close()` has been called.
+    bool IsClosed() const {
+        return closed;
+    }
+
+private:
+    size_t PopInternal(T* dest, size_t dest_len) {
+        size_t output_count = 0;
+        while (output_count < dest_len && CanRead()) {
+            _dbg_assert_(Common, CanRead());
+
+            T* item = &Data()[reader_index];
+            T out_val = std::move(*item);
+            item->~T();
+
+            size_t prev_index = (reader_index + ArraySize - 1) % ArraySize;
+            reader_index = (reader_index + 1) % ArraySize;
+            if (writer_index == prev_index) {
+                writer.notify_one();
+            }
+            dest[output_count++] = std::move(out_val);
+        }
+        return output_count;
+    }
+
+    bool CanRead() const {
+        return reader_index != writer_index;
+    }
+
+    T* Data() {
+        return static_cast<T*>(static_cast<void*>(&storage));
+    }
+
+    /// Storage for entries
+    typename std::aligned_storage<ArraySize * sizeof(T),
+                                  std::alignment_of<T>::value>::type storage;
+
+    /// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the
+    /// queue has been closed.
+    size_t writer_index = 0, reader_index = 0;
+    // True if the queue has been closed.
+    bool closed = false;
+
+    /// Mutex that protects the entire data structure.
+    std::mutex mutex;
+    /// Signaling wakes up reader which is waiting for storage to be non-empty.
+    std::condition_variable reader;
+    /// Signaling wakes up writer which is waiting for storage to be non-full.
+    std::condition_variable writer;
+};
+
+} // namespace

+ 0 - 319
src/common/console_listener.cpp

@@ -1,319 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#ifdef _WIN32
-#include <windows.h>
-#include <array>
-#endif
-
-#include "common/common.h"
-#include "common/log_manager.h" // Common
-#include "common/console_listener.h" // Common
-
-ConsoleListener::ConsoleListener()
-{
-#ifdef _WIN32
-    hConsole = NULL;
-    bUseColor = true;
-#else
-    bUseColor = isatty(fileno(stdout));
-#endif
-}
-
-ConsoleListener::~ConsoleListener()
-{
-    Close();
-}
-
-// 100, 100, "Dolphin Log Console"
-// Open console window - width and height is the size of console window
-// Name is the window title
-void ConsoleListener::Open(bool Hidden, int Width, int Height, const char *Title)
-{
-#ifdef _WIN32
-    if (!GetConsoleWindow())
-    {
-        // Open the console window and create the window handle for GetStdHandle()
-        AllocConsole();
-        // Hide
-        if (Hidden) ShowWindow(GetConsoleWindow(), SW_HIDE);
-        // Save the window handle that AllocConsole() created
-        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
-        // Set the console window title
-        SetConsoleTitle(Common::UTF8ToTStr(Title).c_str());
-        // Set letter space
-        LetterSpace(80, 4000);
-        //MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);
-    }
-    else
-    {
-        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
-    }
-#endif
-}
-
-void ConsoleListener::UpdateHandle()
-{
-#ifdef _WIN32
-    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
-#endif
-}
-
-// Close the console window and close the eventual file handle
-void ConsoleListener::Close()
-{
-#ifdef _WIN32
-    if (hConsole == NULL)
-        return;
-    FreeConsole();
-    hConsole = NULL;
-#else
-    fflush(NULL);
-#endif
-}
-
-bool ConsoleListener::IsOpen()
-{
-#ifdef _WIN32
-    return (hConsole != NULL);
-#else
-    return true;
-#endif
-}
-
-/*
-  LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are
-    dependent on each other, that's the reason for the additional checks.
-*/
-void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst)
-{
-#ifdef _WIN32
-    BOOL SB, SW;
-    if (BufferFirst)
-    {
-        // Change screen buffer size
-        COORD Co = {BufferWidth, BufferHeight};
-        SB = SetConsoleScreenBufferSize(hConsole, Co);
-        // Change the screen buffer window size
-        SMALL_RECT coo = {0,0,ScreenWidth, ScreenHeight}; // top, left, right, bottom
-        SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
-    }
-    else
-    {
-        // Change the screen buffer window size
-        SMALL_RECT coo = {0,0, ScreenWidth, ScreenHeight}; // top, left, right, bottom
-        SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
-        // Change screen buffer size
-        COORD Co = {BufferWidth, BufferHeight};
-        SB = SetConsoleScreenBufferSize(hConsole, Co);
-    }
-#endif
-}
-void ConsoleListener::LetterSpace(int Width, int Height)
-{
-#ifdef _WIN32
-    // Get console info
-    CONSOLE_SCREEN_BUFFER_INFO ConInfo;
-    GetConsoleScreenBufferInfo(hConsole, &ConInfo);
-
-    //
-    int OldBufferWidth = ConInfo.dwSize.X;
-    int OldBufferHeight = ConInfo.dwSize.Y;
-    int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left);
-    int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top);
-    //
-    int NewBufferWidth = Width;
-    int NewBufferHeight = Height;
-    int NewScreenWidth = NewBufferWidth - 1;
-    int NewScreenHeight = OldScreenHeight;
-
-    // Width
-    BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1));
-    // Height
-    BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1));
-
-    // Resize the window too
-    //MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);
-#endif
-}
-#ifdef _WIN32
-COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth)
-{
-    COORD Ret = {0, 0};
-    // Full rows
-    int Step = (int)floor((float)BytesRead / (float)BufferWidth);
-    Ret.Y += Step;
-    // Partial row
-    Ret.X = BytesRead - (BufferWidth * Step);
-    return Ret;
-}
-#endif
-void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize)
-{
-#ifdef _WIN32
-    // Check size
-    if (Width < 8 || Height < 12) return;
-
-    bool DBef = true;
-    bool DAft = true;
-    std::string SLog = "";
-
-    const HWND hWnd = GetConsoleWindow();
-    const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
-
-    // Get console info
-    CONSOLE_SCREEN_BUFFER_INFO ConInfo;
-    GetConsoleScreenBufferInfo(hConsole, &ConInfo);
-    DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y;
-
-    // ---------------------------------------------------------------------
-    //  Save the current text
-    // ------------------------
-    DWORD cCharsRead = 0;
-    COORD coordScreen = { 0, 0 };
-
-    static const int MAX_BYTES = 1024 * 16;
-
-    std::vector<std::array<TCHAR, MAX_BYTES>> Str;
-    std::vector<std::array<WORD, MAX_BYTES>> Attr;
-
-    // ReadConsoleOutputAttribute seems to have a limit at this level
-    static const int ReadBufferSize = MAX_BYTES - 32;
-
-    DWORD cAttrRead = ReadBufferSize;
-    DWORD BytesRead = 0;
-    while (BytesRead < BufferSize)
-    {
-        Str.resize(Str.size() + 1);
-        if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead))
-            SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
-
-        Attr.resize(Attr.size() + 1);
-        if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead))
-            SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
-
-        // Break on error
-        if (cAttrRead == 0) break;
-        BytesRead += cAttrRead;
-        coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X);
-    }
-    // Letter space
-    int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f);
-    int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f);
-    int LBufWidth = LWidth + 1;
-    int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth);
-    // Change screen buffer size
-    LetterSpace(LBufWidth, LBufHeight);
-
-
-    ClearScreen(true);
-    coordScreen.Y = 0;
-    coordScreen.X = 0;
-    DWORD cCharsWritten = 0;
-
-    int BytesWritten = 0;
-    DWORD cAttrWritten = 0;
-    for (size_t i = 0; i < Attr.size(); i++)
-    {
-        if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten))
-            SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
-        if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten))
-            SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
-
-        BytesWritten += cAttrWritten;
-        coordScreen = GetCoordinates(BytesWritten, LBufWidth);
-    }
-
-    const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X;
-    COORD Coo = GetCoordinates(OldCursor, LBufWidth);
-    SetConsoleCursorPosition(hConsole, Coo);
-
-    if (SLog.length() > 0) Log(LogTypes::LNOTICE, SLog.c_str());
-
-    // Resize the window too
-    if (Resize) MoveWindow(GetConsoleWindow(), Left,Top, (Width + 100),Height, true);
-#endif
-}
-
-void ConsoleListener::Log(LogTypes::LOG_LEVELS Level, const char *Text)
-{
-#if defined(_WIN32)
-    WORD Color;
-
-    switch (Level)
-    {
-    case OS_LEVEL: // light yellow
-        Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
-        break;
-    case NOTICE_LEVEL: // light green
-        Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
-        break;
-    case ERROR_LEVEL: // light red
-        Color = FOREGROUND_RED | FOREGROUND_INTENSITY;
-        break;
-    case WARNING_LEVEL: // light purple
-        Color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
-        break;
-    case INFO_LEVEL: // cyan
-        Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
-        break;
-    case DEBUG_LEVEL: // gray
-        Color = FOREGROUND_INTENSITY;
-        break;
-    default: // off-white
-        Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
-        break;
-    }
-    SetConsoleTextAttribute(hConsole, Color);
-    printf(Text);
-#else
-    char ColorAttr[16] = "";
-    char ResetAttr[16] = "";
-
-    if (bUseColor)
-    {
-        strcpy(ResetAttr, "\033[0m");
-        switch (Level)
-        {
-        case NOTICE_LEVEL: // light green
-            strcpy(ColorAttr, "\033[92m");
-            break;
-        case ERROR_LEVEL: // light red
-            strcpy(ColorAttr, "\033[91m");
-            break;
-        case WARNING_LEVEL: // light yellow
-            strcpy(ColorAttr, "\033[93m");
-            break;
-        default:
-            break;
-        }
-    }
-    fprintf(stderr, "%s%s%s", ColorAttr, Text, ResetAttr);
-#endif
-}
-// Clear console screen
-void ConsoleListener::ClearScreen(bool Cursor)
-{
-#if defined(_WIN32)
-    COORD coordScreen = { 0, 0 };
-    DWORD cCharsWritten;
-    CONSOLE_SCREEN_BUFFER_INFO csbi;
-    DWORD dwConSize;
-
-    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
-
-    GetConsoleScreenBufferInfo(hConsole, &csbi);
-    dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
-    // Write space to the entire console
-    FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten);
-    GetConsoleScreenBufferInfo(hConsole, &csbi);
-    FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
-    // Reset cursor
-    if (Cursor) SetConsoleCursorPosition(hConsole, coordScreen);
-#endif
-}
-
-

+ 0 - 38
src/common/console_listener.h

@@ -1,38 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/log_manager.h"
-
-#ifdef _WIN32
-#include <windows.h>
-#endif
-
-class ConsoleListener : public LogListener
-{
-public:
-    ConsoleListener();
-    ~ConsoleListener();
-
-    void Open(bool Hidden = false, int Width = 100, int Height = 100, const char * Name = "Console");
-    void UpdateHandle();
-    void Close();
-    bool IsOpen();
-    void LetterSpace(int Width, int Height);
-    void BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst);
-    void PixelSpace(int Left, int Top, int Width, int Height, bool);
-#ifdef _WIN32
-    COORD GetCoordinates(int BytesRead, int BufferWidth);
-#endif
-    void Log(LogTypes::LOG_LEVELS, const char *Text) override;
-    void ClearScreen(bool Cursor = true);
-
-private:
-#ifdef _WIN32
-    HWND GetHwnd(void);
-    HANDLE hConsole;
-#endif
-    bool bUseColor;
-};

+ 2 - 2
src/common/cpu_detect.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 

+ 1 - 1
src/common/emu_window.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include "emu_window.h"

+ 1 - 1
src/common/emu_window.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 12 - 12
src/common/extended_trace.cpp

@@ -82,7 +82,7 @@ static void InitSymbolPath( PSTR lpszSymbolPath, PCSTR lpszIniPath )
     }
 
     // Add user defined path
-    if ( lpszIniPath != NULL )
+    if ( lpszIniPath != nullptr )
         if ( lpszIniPath[0] != '\0' )
         {
             strcat( lpszSymbolPath, ";" );
@@ -138,7 +138,7 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L
     DWORD             dwSymSize = 10000;
     TCHAR             lpszUnDSymbol[BUFFERSIZE]=_T("?");
     CHAR              lpszNonUnicodeUnDSymbol[BUFFERSIZE]="?";
-    LPTSTR            lpszParamSep = NULL;
+    LPTSTR            lpszParamSep = nullptr;
     LPTSTR            lpszParsed = lpszUnDSymbol;
     PIMAGEHLP_SYMBOL  pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, dwSymSize );
 
@@ -187,13 +187,13 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L
 
         // 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)") ) == NULL && _tcsstr( lpszUnDSymbol, _T("()") ) == NULL)
+        if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == nullptr && _tcsstr( lpszUnDSymbol, _T("()") ) == nullptr)
         {
             ULONG index = 0;
             for( ; ; index++ )
             {
                 lpszParamSep = _tcschr( lpszParsed, _T(',') );
-                if ( lpszParamSep == NULL )
+                if ( lpszParamSep == nullptr )
                     break;
 
                 *lpszParamSep = _T('\0');
@@ -205,7 +205,7 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L
             }
 
             lpszParamSep = _tcschr( lpszParsed, _T(')') );
-            if ( lpszParamSep != NULL )
+            if ( lpszParamSep != nullptr )
             {
                 *lpszParamSep = _T('\0');
 
@@ -248,7 +248,7 @@ static BOOL GetSourceInfoFromAddress( UINT address, LPTSTR lpszSourceInfo )
         PCSTR2LPTSTR( lineInfo.FileName, lpszFileName );
         TCHAR fname[_MAX_FNAME];
         TCHAR ext[_MAX_EXT];
-        _tsplitpath(lpszFileName, NULL, NULL, fname, ext);
+        _tsplitpath(lpszFileName, nullptr, nullptr, fname, ext);
         _stprintf( lpszSourceInfo, _T("%s%s(%d)"), fname, ext, lineInfo.LineNumber );
         ret = TRUE;
     }
@@ -332,11 +332,11 @@ void StackTrace( HANDLE hThread, const char* lpszMessage, FILE *file )
                 hProcess,
                 hThread,
                 &callStack,
-                NULL,
-                NULL,
+                nullptr,
+                nullptr,
                 SymFunctionTableAccess,
                 SymGetModuleBase,
-                NULL);
+                nullptr);
 
             if ( index == 0 )
                 continue;
@@ -389,11 +389,11 @@ void StackTrace(HANDLE hThread, const char* lpszMessage, FILE *file, DWORD eip,
                 hProcess,
                 hThread,
                 &callStack,
-                NULL,
-                NULL,
+                nullptr,
+                nullptr,
                 SymFunctionTableAccess,
                 SymGetModuleBase,
-                NULL);
+                nullptr);
 
             if ( index == 0 )
                 continue;

+ 2 - 2
src/common/fifo_queue.h

@@ -57,7 +57,7 @@ public:
         // advance the read pointer
         m_read_ptr = m_read_ptr->next;
         // set the next element to NULL to stop the recursive deletion
-        tmpptr->next = NULL;
+        tmpptr->next = nullptr;
         delete tmpptr;    // this also deletes the element
     }
 
@@ -86,7 +86,7 @@ private:
     class ElementPtr
     {
     public:
-        ElementPtr() : current(NULL), next(NULL) {}
+        ElementPtr() : current(nullptr), next(nullptr) {}
 
         ~ElementPtr()
         {

+ 2 - 2
src/common/file_search.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 

+ 2 - 2
src/common/file_search.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 61 - 69
src/common/file_util.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 
@@ -88,7 +88,7 @@ bool IsDirectory(const std::string &filename)
 #endif
 
     if (result < 0) {
-        WARN_LOG(COMMON, "IsDirectory: stat failed on %s: %s",
+        LOG_WARNING(Common_Filesystem, "stat failed on %s: %s",
                  filename.c_str(), GetLastErrorMsg());
         return false;
     }
@@ -100,33 +100,33 @@ bool IsDirectory(const std::string &filename)
 // Doesn't supports deleting a directory
 bool Delete(const std::string &filename)
 {
-    INFO_LOG(COMMON, "Delete: file %s", filename.c_str());
+    LOG_INFO(Common_Filesystem, "file %s", filename.c_str());
 
     // Return true because we care about the file no
     // being there, not the actual delete.
     if (!Exists(filename))
     {
-        WARN_LOG(COMMON, "Delete: %s does not exist", filename.c_str());
+        LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str());
         return true;
     }
 
     // We can't delete a directory
     if (IsDirectory(filename))
     {
-        WARN_LOG(COMMON, "Delete failed: %s is a directory", filename.c_str());
+        LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
         return false;
     }
 
 #ifdef _WIN32
     if (!DeleteFile(Common::UTF8ToTStr(filename).c_str()))
     {
-        WARN_LOG(COMMON, "Delete: DeleteFile failed on %s: %s",
+        LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s",
                  filename.c_str(), GetLastErrorMsg());
         return false;
     }
 #else
     if (unlink(filename.c_str()) == -1) {
-        WARN_LOG(COMMON, "Delete: unlink failed on %s: %s",
+        LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s",
                  filename.c_str(), GetLastErrorMsg());
         return false;
     }
@@ -138,17 +138,17 @@ bool Delete(const std::string &filename)
 // Returns true if successful, or path already exists.
 bool CreateDir(const std::string &path)
 {
-    INFO_LOG(COMMON, "CreateDir: directory %s", path.c_str());
+    LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
 #ifdef _WIN32
-    if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), NULL))
+    if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), nullptr))
         return true;
     DWORD error = GetLastError();
     if (error == ERROR_ALREADY_EXISTS)
     {
-        WARN_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());
+        LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str());
         return true;
     }
-    ERROR_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: %i", path.c_str(), error);
+    LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
     return false;
 #else
     if (mkdir(path.c_str(), 0755) == 0)
@@ -158,11 +158,11 @@ bool CreateDir(const std::string &path)
 
     if (err == EEXIST)
     {
-        WARN_LOG(COMMON, "CreateDir: mkdir failed on %s: already exists", path.c_str());
+        LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
         return true;
     }
 
-    ERROR_LOG(COMMON, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));
+    LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err));
     return false;
 #endif
 }
@@ -171,11 +171,11 @@ bool CreateDir(const std::string &path)
 bool CreateFullPath(const std::string &fullPath)
 {
     int panicCounter = 100;
-    INFO_LOG(COMMON, "CreateFullPath: path %s", fullPath.c_str());
+    LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
 
     if (FileUtil::Exists(fullPath))
     {
-        INFO_LOG(COMMON, "CreateFullPath: path exists %s", fullPath.c_str());
+        LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str());
         return true;
     }
 
@@ -192,7 +192,7 @@ bool CreateFullPath(const std::string &fullPath)
         // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
         std::string const subPath(fullPath.substr(0, position + 1));
         if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) {
-            ERROR_LOG(COMMON, "CreateFullPath: directory creation failed");
+            LOG_ERROR(Common, "CreateFullPath: directory creation failed");
             return false;
         }
 
@@ -200,7 +200,7 @@ bool CreateFullPath(const std::string &fullPath)
         panicCounter--;
         if (panicCounter <= 0)
         {
-            ERROR_LOG(COMMON, "CreateFullPath: directory structure is too deep");
+            LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
             return false;
         }
         position++;
@@ -211,12 +211,12 @@ bool CreateFullPath(const std::string &fullPath)
 // Deletes a directory filename, returns true on success
 bool DeleteDir(const std::string &filename)
 {
-    INFO_LOG(COMMON, "DeleteDir: directory %s", filename.c_str());
+    LOG_INFO(Common_Filesystem, "directory %s", filename.c_str());
 
     // check if a directory
     if (!FileUtil::IsDirectory(filename))
     {
-        ERROR_LOG(COMMON, "DeleteDir: Not a directory %s", filename.c_str());
+        LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
         return false;
     }
 
@@ -227,7 +227,7 @@ bool DeleteDir(const std::string &filename)
     if (rmdir(filename.c_str()) == 0)
         return true;
 #endif
-    ERROR_LOG(COMMON, "DeleteDir: %s: %s", filename.c_str(), GetLastErrorMsg());
+    LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
 
     return false;
 }
@@ -235,11 +235,11 @@ bool DeleteDir(const std::string &filename)
 // renames file srcFilename to destFilename, returns true on success
 bool Rename(const std::string &srcFilename, const std::string &destFilename)
 {
-    INFO_LOG(COMMON, "Rename: %s --> %s",
+    LOG_TRACE(Common_Filesystem, "%s --> %s",
             srcFilename.c_str(), destFilename.c_str());
     if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
         return true;
-    ERROR_LOG(COMMON, "Rename: failed %s --> %s: %s",
+    LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s",
               srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
     return false;
 }
@@ -247,13 +247,13 @@ bool Rename(const std::string &srcFilename, const std::string &destFilename)
 // copies file srcFilename to destFilename, returns true on success
 bool Copy(const std::string &srcFilename, const std::string &destFilename)
 {
-    INFO_LOG(COMMON, "Copy: %s --> %s",
+    LOG_TRACE(Common_Filesystem, "%s --> %s",
             srcFilename.c_str(), destFilename.c_str());
 #ifdef _WIN32
     if (CopyFile(Common::UTF8ToTStr(srcFilename).c_str(), Common::UTF8ToTStr(destFilename).c_str(), FALSE))
         return true;
 
-    ERROR_LOG(COMMON, "Copy: failed %s --> %s: %s",
+    LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s",
             srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
     return false;
 #else
@@ -267,7 +267,7 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename)
     FILE *input = fopen(srcFilename.c_str(), "rb");
     if (!input)
     {
-        ERROR_LOG(COMMON, "Copy: input failed %s --> %s: %s",
+        LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s",
                 srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
         return false;
     }
@@ -277,7 +277,7 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename)
     if (!output)
     {
         fclose(input);
-        ERROR_LOG(COMMON, "Copy: output failed %s --> %s: %s",
+        LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s",
                 srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
         return false;
     }
@@ -291,8 +291,8 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename)
         {
             if (ferror(input) != 0)
             {
-                ERROR_LOG(COMMON,
-                        "Copy: failed reading from source, %s --> %s: %s",
+                LOG_ERROR(Common_Filesystem,
+                        "failed reading from source, %s --> %s: %s",
                         srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
                 goto bail;
             }
@@ -302,8 +302,8 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename)
         int wnum = fwrite(buffer, sizeof(char), rnum, output);
         if (wnum != rnum)
         {
-            ERROR_LOG(COMMON,
-                    "Copy: failed writing to output, %s --> %s: %s",
+            LOG_ERROR(Common_Filesystem,
+                    "failed writing to output, %s --> %s: %s",
                     srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
             goto bail;
         }
@@ -326,13 +326,13 @@ u64 GetSize(const std::string &filename)
 {
     if (!Exists(filename))
     {
-        WARN_LOG(COMMON, "GetSize: failed %s: No such file", filename.c_str());
+        LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
         return 0;
     }
 
     if (IsDirectory(filename))
     {
-        WARN_LOG(COMMON, "GetSize: failed %s: is a directory", filename.c_str());
+        LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
         return 0;
     }
 
@@ -343,12 +343,12 @@ u64 GetSize(const std::string &filename)
     if (stat64(filename.c_str(), &buf) == 0)
 #endif
     {
-        DEBUG_LOG(COMMON, "GetSize: %s: %lld",
+        LOG_TRACE(Common_Filesystem, "%s: %lld",
                 filename.c_str(), (long long)buf.st_size);
         return buf.st_size;
     }
 
-    ERROR_LOG(COMMON, "GetSize: Stat failed %s: %s",
+    LOG_ERROR(Common_Filesystem, "Stat failed %s: %s",
             filename.c_str(), GetLastErrorMsg());
     return 0;
 }
@@ -358,7 +358,7 @@ u64 GetSize(const int fd)
 {
     struct stat64 buf;
     if (fstat64(fd, &buf) != 0) {
-        ERROR_LOG(COMMON, "GetSize: stat failed %i: %s",
+        LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s",
             fd, GetLastErrorMsg());
         return 0;
     }
@@ -371,13 +371,13 @@ u64 GetSize(FILE *f)
     // can't use off_t here because it can be 32-bit
     u64 pos = ftello(f);
     if (fseeko(f, 0, SEEK_END) != 0) {
-        ERROR_LOG(COMMON, "GetSize: seek failed %p: %s",
+        LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s",
               f, GetLastErrorMsg());
         return 0;
     }
     u64 size = ftello(f);
     if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
-        ERROR_LOG(COMMON, "GetSize: seek failed %p: %s",
+        LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s",
               f, GetLastErrorMsg());
         return 0;
     }
@@ -387,11 +387,11 @@ u64 GetSize(FILE *f)
 // creates an empty file filename, returns true on success
 bool CreateEmptyFile(const std::string &filename)
 {
-    INFO_LOG(COMMON, "CreateEmptyFile: %s", filename.c_str());
+    LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
 
     if (!FileUtil::IOFile(filename, "wb"))
     {
-        ERROR_LOG(COMMON, "CreateEmptyFile: failed %s: %s",
+        LOG_ERROR(Common_Filesystem, "failed %s: %s",
                   filename.c_str(), GetLastErrorMsg());
         return false;
     }
@@ -404,7 +404,7 @@ bool CreateEmptyFile(const std::string &filename)
 // results into parentEntry. Returns the number of files+directories found
 u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry)
 {
-    INFO_LOG(COMMON, "ScanDirectoryTree: directory %s", directory.c_str());
+    LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
     // How many files + directories we found
     u32 foundEntries = 0;
 #ifdef _WIN32
@@ -423,7 +423,7 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry)
         FSTEntry entry;
         const std::string virtualName(Common::TStrToUTF8(ffd.cFileName));
 #else
-    struct dirent dirent, *result = NULL;
+    struct dirent dirent, *result = nullptr;
 
     DIR *dirp = opendir(directory.c_str());
     if (!dirp)
@@ -474,7 +474,7 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry)
 // Deletes the given directory and anything under it. Returns true on success.
 bool DeleteDirRecursively(const std::string &directory)
 {
-    INFO_LOG(COMMON, "DeleteDirRecursively: %s", directory.c_str());
+    LOG_TRACE(Common_Filesystem, "%s", directory.c_str());
 #ifdef _WIN32
     // Find the first file in the directory.
     WIN32_FIND_DATA ffd;
@@ -491,7 +491,7 @@ bool DeleteDirRecursively(const std::string &directory)
     {
         const std::string virtualName(Common::TStrToUTF8(ffd.cFileName));
 #else
-    struct dirent dirent, *result = NULL;
+    struct dirent dirent, *result = nullptr;
     DIR *dirp = opendir(directory.c_str());
     if (!dirp)
         return false;
@@ -552,7 +552,7 @@ void CopyDir(const std::string &source_path, const std::string &dest_path)
     if (!FileUtil::Exists(source_path)) return;
     if (!FileUtil::Exists(dest_path)) FileUtil::CreateFullPath(dest_path);
 
-    struct dirent dirent, *result = NULL;
+    struct dirent dirent, *result = nullptr;
     DIR *dirp = opendir(source_path.c_str());
     if (!dirp) return;
 
@@ -586,11 +586,11 @@ std::string GetCurrentDir()
 {
     char *dir;
     // Get the current working directory (getcwd uses malloc)
-    if (!(dir = __getcwd(NULL, 0))) {
+    if (!(dir = __getcwd(nullptr, 0))) {
 
-        ERROR_LOG(COMMON, "GetCurrentDirectory failed: %s",
+        LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s",
                 GetLastErrorMsg());
-        return NULL;
+        return nullptr;
     }
     std::string strDir = dir;
     free(dir);
@@ -626,7 +626,7 @@ std::string& GetExeDirectory()
     if (DolphinPath.empty())
     {
         TCHAR Dolphin_exe_Path[2048];
-        GetModuleFileName(NULL, Dolphin_exe_Path, 2048);
+        GetModuleFileName(nullptr, Dolphin_exe_Path, 2048);
         DolphinPath = Common::TStrToUTF8(Dolphin_exe_Path);
         DolphinPath = DolphinPath.substr(0, DolphinPath.find_last_of('\\'));
     }
@@ -647,7 +647,7 @@ std::string GetSysDirectory()
 #endif
     sysDir += DIR_SEP;
 
-    INFO_LOG(COMMON, "GetSysDirectory: Setting to %s:", sysDir.c_str());
+    LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str());
     return sysDir;
 }
 
@@ -676,6 +676,9 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
         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_SAVEDATA_IDX]       = paths[D_USER_IDX] + SAVEDATA_DIR DIR_SEP;
+        paths[D_SYSDATA_IDX]        = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
+        paths[D_SYSSAVEDATA_IDX]    = paths[D_USER_IDX] + SYSSAVEDATA_DIR DIR_SEP;
         paths[D_SHADERCACHE_IDX]    = paths[D_USER_IDX] + SHADERCACHE_DIR DIR_SEP;
         paths[D_SHADERS_IDX]        = paths[D_USER_IDX] + SHADERS_DIR DIR_SEP;
         paths[D_STATESAVES_IDX]     = paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP;
@@ -694,7 +697,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
     {
         if (!FileUtil::IsDirectory(newPath))
         {
-            WARN_LOG(COMMON, "Invalid path specified %s", newPath.c_str());
+            LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
             return paths[DirIDX];
         }
         else
@@ -717,6 +720,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
             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_SAVEDATA_IDX]       = paths[D_USER_IDX] + SAVEDATA_DIR DIR_SEP;
+            paths[D_SYSSAVEDATA_IDX]    = paths[D_USER_IDX] + SYSSAVEDATA_DIR DIR_SEP;
             paths[D_SHADERCACHE_IDX]    = paths[D_USER_IDX] + SHADERCACHE_DIR DIR_SEP;
             paths[D_SHADERS_IDX]        = paths[D_USER_IDX] + SHADERS_DIR DIR_SEP;
             paths[D_STATESAVES_IDX]     = paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP;
@@ -753,19 +758,6 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new
     return paths[DirIDX];
 }
 
-//std::string GetThemeDir(const std::string& theme_name)
-//{
-//    std::string dir = FileUtil::GetUserPath(D_THEMES_IDX) + theme_name + "/";
-//
-//#if !defined(_WIN32)
-//    // If theme does not exist in user's dir load from shared directory
-//    if (!FileUtil::Exists(dir))
-//        dir = SHARED_USER_DIR THEMES_DIR "/" + theme_name + "/";
-//#endif
-//
-//    return dir;
-//}
-
 size_t WriteStringToFile(bool text_file, const std::string &str, const char *filename)
 {
     return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
@@ -826,7 +818,7 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
 }
 
 IOFile::IOFile()
-    : m_file(NULL), m_good(true)
+    : m_file(nullptr), m_good(true)
 {}
 
 IOFile::IOFile(std::FILE* file)
@@ -834,7 +826,7 @@ IOFile::IOFile(std::FILE* file)
 {}
 
 IOFile::IOFile(const std::string& filename, const char openmode[])
-    : m_file(NULL), m_good(true)
+    : m_file(nullptr), m_good(true)
 {
     Open(filename, openmode);
 }
@@ -845,7 +837,7 @@ IOFile::~IOFile()
 }
 
 IOFile::IOFile(IOFile&& other)
-    : m_file(NULL), m_good(true)
+    : m_file(nullptr), m_good(true)
 {
     Swap(other);
 }
@@ -880,14 +872,14 @@ bool IOFile::Close()
     if (!IsOpen() || 0 != std::fclose(m_file))
         m_good = false;
 
-    m_file = NULL;
+    m_file = nullptr;
     return m_good;
 }
 
 std::FILE* IOFile::ReleaseHandle()
 {
     std::FILE* const ret = m_file;
-    m_file = NULL;
+    m_file = nullptr;
     return ret;
 }
 

+ 7 - 4
src/common/file_util.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
@@ -27,6 +27,9 @@ enum {
     D_STATESAVES_IDX,
     D_SCREENSHOTS_IDX,
     D_SDMC_IDX,
+    D_SAVEDATA_IDX,
+    D_SYSDATA_IDX,
+    D_SYSSAVEDATA_IDX,
     D_HIRESTEXTURES_IDX,
     D_DUMP_IDX,
     D_DUMPFRAMES_IDX,
@@ -202,11 +205,11 @@ public:
         return WriteArray(reinterpret_cast<const char*>(data), length);
     }
 
-    bool IsOpen() { return NULL != m_file; }
+    bool IsOpen() { return nullptr != m_file; }
 
     // m_good is set to false when a read, write or other function fails
     bool IsGood() {    return m_good; }
-    operator void*() { return m_good ? m_file : NULL; }
+    operator void*() { return m_good ? m_file : nullptr; }
 
     std::FILE* ReleaseHandle();
 

+ 2 - 2
src/common/hash.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 

+ 2 - 2
src/common/hash.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 1 - 1
src/common/key_map.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include "key_map.h"

+ 1 - 1
src/common/key_map.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 3 - 3
src/common/linear_disk_cache.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
@@ -70,7 +70,7 @@ public:
             // good header, read some key/value pairs
             K key;
 
-            V *value = NULL;
+            V *value = nullptr;
             u32 value_size;
             u32 entry_number;
 

+ 12 - 121
src/common/log.h

@@ -1,108 +1,12 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
 
-#ifndef LOGGING
-#define LOGGING
-#endif
-
-enum {
-    OS_LEVEL,       // Printed by the emulated operating system
-    NOTICE_LEVEL,   // VERY important information that is NOT errors. Like startup and OSReports.
-    ERROR_LEVEL,    // Critical errors
-    WARNING_LEVEL,  // Something is suspicious.
-    INFO_LEVEL,     // General information.
-    DEBUG_LEVEL,    // Detailed debugging - might make things slow.
-};
-
-namespace LogTypes
-{
-
-enum LOG_TYPE {
-    ACTIONREPLAY,
-    AUDIO,
-    AUDIO_INTERFACE,
-    BOOT,
-    COMMANDPROCESSOR,
-    COMMON,
-    CONSOLE,
-    CONFIG,
-    DISCIO,
-    FILEMON,
-    DSPHLE,
-    DSPLLE,
-    DSP_MAIL,
-    DSPINTERFACE,
-    DVDINTERFACE,
-    DYNA_REC,
-    EXPANSIONINTERFACE,
-    GDB_STUB,
-    ARM11,
-    GSP,
-    OSHLE,
-    MASTER_LOG,
-    MEMMAP,
-    MEMCARD_MANAGER,
-    OSREPORT,
-    PAD,
-    PROCESSORINTERFACE,
-    PIXELENGINE,
-    SERIALINTERFACE,
-    SP1,
-    STREAMINGINTERFACE,
-    VIDEO,
-    VIDEOINTERFACE,
-    LOADER,
-    FILESYS,
-    WII_IPC_DVD,
-    WII_IPC_ES,
-    WII_IPC_FILEIO,
-    WII_IPC_HID,
-    KERNEL,
-    SVC,
-    NDMA,
-    HLE,
-    RENDER,
-    GPU,
-    HW,
-    TIME,
-    NETPLAY,
-    GUI,
-
-    NUMBER_OF_LOGS // Must be last
-};
-
-// FIXME: should this be removed?
-enum LOG_LEVELS {
-    LOS = OS_LEVEL,
-    LNOTICE = NOTICE_LEVEL,
-    LERROR = ERROR_LEVEL,
-    LWARNING = WARNING_LEVEL,
-    LINFO = INFO_LEVEL,
-    LDEBUG = DEBUG_LEVEL,
-};
-
-#define LOGTYPES_LEVELS LogTypes::LOG_LEVELS
-#define LOGTYPES_TYPE LogTypes::LOG_TYPE
-
-}  // namespace
-
-void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int line,
-    const char* function, const char* fmt, ...)
-#ifdef __GNUC__
-        __attribute__((format(printf, 6, 7)))
-#endif
-        ;
-
-#if defined LOGGING || defined _DEBUG || defined DEBUGFAST
-#define MAX_LOGLEVEL LDEBUG
-#else
-#ifndef MAX_LOGLEVEL
-#define MAX_LOGLEVEL LWARNING
-#endif // loglevel
-#endif // logging
+#include "common/common_funcs.h"
+#include "common/msg_handler.h"
+#include "common/logging/log.h"
 
 #ifdef MSVC_VER
 #ifndef __func__
@@ -110,29 +14,16 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int
 #endif
 #endif
 
-// Let the compiler optimize this out
-#define GENERIC_LOG(t, v, ...) { \
-    if (v <= LogTypes::MAX_LOGLEVEL) \
-        GenericLog(v, t, __FILE__, __LINE__, __func__, __VA_ARGS__); \
-    }
-
-#define OS_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LOS, __VA_ARGS__) } while (0)
-#define ERROR_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LERROR, __VA_ARGS__) } while (0)
-#define WARN_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LWARNING, __VA_ARGS__) } while (0)
-#define NOTICE_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LNOTICE, __VA_ARGS__) } while (0)
-#define INFO_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LINFO, __VA_ARGS__) } while (0)
-#define DEBUG_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LDEBUG, __VA_ARGS__) } while (0)
-
-#if MAX_LOGLEVEL >= DEBUG_LEVEL
+#if _DEBUG
 #define _dbg_assert_(_t_, _a_) \
     if (!(_a_)) {\
-        ERROR_LOG(_t_, "Error...\n\n  Line: %d\n  File: %s\n  Time: %s\n\nIgnore and continue?", \
+        LOG_CRITICAL(_t_, "Error...\n\n  Line: %d\n  File: %s\n  Time: %s\n\nIgnore and continue?", \
                        __LINE__, __FILE__, __TIME__); \
         if (!PanicYesNo("*** Assertion (see log)***\n")) {Crash();} \
     }
 #define _dbg_assert_msg_(_t_, _a_, ...)\
     if (!(_a_)) {\
-        ERROR_LOG(_t_, __VA_ARGS__); \
+        LOG_CRITICAL(_t_, __VA_ARGS__); \
         if (!PanicYesNo(__VA_ARGS__)) {Crash();} \
     }
 #define _dbg_update_() Host_UpdateLogDisplay();
@@ -144,12 +35,12 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int
 #define _dbg_assert_(_t_, _a_) {}
 #define _dbg_assert_msg_(_t_, _a_, _desc_, ...) {}
 #endif // dbg_assert
-#endif // MAX_LOGLEVEL DEBUG
+#endif
 
 #define _assert_(_a_) _dbg_assert_(MASTER_LOG, _a_)
 
 #ifndef GEKKO
-#ifdef MSVC_VER
+#ifdef _WIN32
 #define _assert_msg_(_t_, _a_, _fmt_, ...)        \
     if (!(_a_)) {\
         if (!PanicYesNo(_fmt_, __VA_ARGS__)) {Crash();} \
@@ -159,7 +50,7 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int
     if (!(_a_)) {\
         if (!PanicYesNo(_fmt_, ##__VA_ARGS__)) {Crash();} \
     }
-#endif // MSVC_VER
+#endif // _WIN32
 #else // GEKKO
 #define _assert_msg_(_t_, _a_, _fmt_, ...)
-#endif
+#endif

+ 0 - 199
src/common/log_manager.cpp

@@ -1,199 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "common/log_manager.h"
-#include "common/console_listener.h"
-#include "common/timer.h"
-
-void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
-    const char* function, const char* fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-
-    if (LogManager::GetInstance()) {
-        LogManager::GetInstance()->Log(level, type,
-            file, line, function, fmt, args);
-    }
-    va_end(args);
-}
-
-LogManager *LogManager::m_logManager = NULL;
-
-LogManager::LogManager()
-{
-    // create log files
-    m_Log[LogTypes::MASTER_LOG]         = new LogContainer("*",                 "Master Log");
-    m_Log[LogTypes::BOOT]               = new LogContainer("BOOT",              "Boot");
-    m_Log[LogTypes::COMMON]             = new LogContainer("COMMON",            "Common");
-    m_Log[LogTypes::CONFIG]             = new LogContainer("CONFIG",            "Configuration");
-    m_Log[LogTypes::DISCIO]             = new LogContainer("DIO",               "Disc IO");
-    m_Log[LogTypes::FILEMON]            = new LogContainer("FileMon",           "File Monitor");
-    m_Log[LogTypes::PAD]                = new LogContainer("PAD",               "Pad");
-    m_Log[LogTypes::PIXELENGINE]        = new LogContainer("PE",                "PixelEngine");
-    m_Log[LogTypes::COMMANDPROCESSOR]   = new LogContainer("CP",                "CommandProc");
-    m_Log[LogTypes::VIDEOINTERFACE]     = new LogContainer("VI",                "VideoInt");
-    m_Log[LogTypes::SERIALINTERFACE]    = new LogContainer("SI",                "SerialInt");
-    m_Log[LogTypes::PROCESSORINTERFACE] = new LogContainer("PI",                "ProcessorInt");
-    m_Log[LogTypes::MEMMAP]             = new LogContainer("MI",                "MI & memmap");
-    m_Log[LogTypes::SP1]                = new LogContainer("SP1",               "Serial Port 1");
-    m_Log[LogTypes::STREAMINGINTERFACE] = new LogContainer("Stream",            "StreamingInt");
-    m_Log[LogTypes::DSPINTERFACE]       = new LogContainer("DSP",               "DSPInterface");
-    m_Log[LogTypes::DVDINTERFACE]       = new LogContainer("DVD",               "DVDInterface");
-    m_Log[LogTypes::GSP]                = new LogContainer("GSP",               "GSP");
-    m_Log[LogTypes::EXPANSIONINTERFACE] = new LogContainer("EXI",               "ExpansionInt");
-    m_Log[LogTypes::GDB_STUB]           = new LogContainer("GDB_STUB",          "GDB Stub");
-    m_Log[LogTypes::AUDIO_INTERFACE]    = new LogContainer("AI",                "AudioInt");
-    m_Log[LogTypes::ARM11]              = new LogContainer("ARM11",             "ARM11");
-    m_Log[LogTypes::OSHLE]              = new LogContainer("HLE",               "HLE");
-    m_Log[LogTypes::DSPHLE]             = new LogContainer("DSPHLE",            "DSP HLE");
-    m_Log[LogTypes::DSPLLE]             = new LogContainer("DSPLLE",            "DSP LLE");
-    m_Log[LogTypes::DSP_MAIL]           = new LogContainer("DSPMails",          "DSP Mails");
-    m_Log[LogTypes::VIDEO]              = new LogContainer("Video",             "Video Backend");
-    m_Log[LogTypes::AUDIO]              = new LogContainer("Audio",             "Audio Emulator");
-    m_Log[LogTypes::DYNA_REC]           = new LogContainer("JIT",               "JIT");
-    m_Log[LogTypes::CONSOLE]            = new LogContainer("CONSOLE",           "Dolphin Console");
-    m_Log[LogTypes::OSREPORT]           = new LogContainer("OSREPORT",          "OSReport");
-    m_Log[LogTypes::TIME]               = new LogContainer("Time",              "Core Timing");
-    m_Log[LogTypes::LOADER]             = new LogContainer("Loader",            "Loader");
-    m_Log[LogTypes::FILESYS]            = new LogContainer("FileSys",           "File System");
-    m_Log[LogTypes::WII_IPC_HID]        = new LogContainer("WII_IPC_HID",       "WII IPC HID");
-    m_Log[LogTypes::KERNEL]             = new LogContainer("KERNEL",            "KERNEL HLE");
-    m_Log[LogTypes::WII_IPC_DVD]        = new LogContainer("WII_IPC_DVD",       "WII IPC DVD");
-    m_Log[LogTypes::WII_IPC_ES]         = new LogContainer("WII_IPC_ES",        "WII IPC ES");
-    m_Log[LogTypes::WII_IPC_FILEIO]     = new LogContainer("WII_IPC_FILEIO",    "WII IPC FILEIO");
-    m_Log[LogTypes::RENDER]             = new LogContainer("RENDER",            "RENDER");
-    m_Log[LogTypes::GPU]                = new LogContainer("GPU",               "GPU");
-    m_Log[LogTypes::SVC]                = new LogContainer("SVC",               "Supervisor Call HLE");
-    m_Log[LogTypes::NDMA]               = new LogContainer("NDMA",              "NDMA");
-    m_Log[LogTypes::HLE]                = new LogContainer("HLE",               "High Level Emulation");
-    m_Log[LogTypes::HW]                 = new LogContainer("HW",                "Hardware");
-    m_Log[LogTypes::ACTIONREPLAY]       = new LogContainer("ActionReplay",      "ActionReplay");
-    m_Log[LogTypes::MEMCARD_MANAGER]    = new LogContainer("MemCard Manager",   "MemCard Manager");
-    m_Log[LogTypes::NETPLAY]            = new LogContainer("NETPLAY",           "Netplay");
-    m_Log[LogTypes::GUI]                = new LogContainer("GUI",               "GUI");
-
-    m_fileLog = new FileLogListener(FileUtil::GetUserPath(F_MAINLOG_IDX).c_str());
-    m_consoleLog = new ConsoleListener();
-    m_debuggerLog = new DebuggerLogListener();
-
-    for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
-    {
-        m_Log[i]->SetEnable(true);
-        m_Log[i]->AddListener(m_fileLog);
-        m_Log[i]->AddListener(m_consoleLog);
-#ifdef _MSC_VER
-        if (IsDebuggerPresent())
-            m_Log[i]->AddListener(m_debuggerLog);
-#endif
-    }
-
-    m_consoleLog->Open();
-}
-
-LogManager::~LogManager()
-{
-    for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
-    {
-        m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_fileLog);
-        m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_consoleLog);
-        m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_debuggerLog);
-    }
-
-    for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
-        delete m_Log[i];
-
-    delete m_fileLog;
-    delete m_consoleLog;
-    delete m_debuggerLog;
-}
-
-void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file,
-    int line, const char* function, const char *fmt, va_list args)
-{
-    char temp[MAX_MSGLEN];
-    char msg[MAX_MSGLEN * 2];
-    LogContainer *log = m_Log[type];
-
-    if (!log->IsEnabled() || level > log->GetLevel() || ! log->HasListeners())
-        return;
-
-    Common::CharArrayFromFormatV(temp, MAX_MSGLEN, fmt, args);
-
-    static const char level_to_char[7] = "ONEWID";
-    sprintf(msg, "%s %s:%u %c[%s] %s: %s\n", Common::Timer::GetTimeFormatted().c_str(), file, line,
-        level_to_char[(int)level], log->GetShortName(), function, temp);
-
-#ifdef ANDROID
-    Host_SysMessage(msg);
-#endif
-    log->Trigger(level, msg);
-}
-
-void LogManager::Init()
-{
-    m_logManager = new LogManager();
-}
-
-void LogManager::Shutdown()
-{
-    delete m_logManager;
-    m_logManager = NULL;
-}
-
-LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable)
-    : m_enable(enable)
-{
-    strncpy(m_fullName, fullName, 128);
-    strncpy(m_shortName, shortName, 32);
-    m_level = LogTypes::MAX_LOGLEVEL;
-}
-
-// LogContainer
-void LogContainer::AddListener(LogListener *listener)
-{
-    std::lock_guard<std::mutex> lk(m_listeners_lock);
-    m_listeners.insert(listener);
-}
-
-void LogContainer::RemoveListener(LogListener *listener)
-{
-    std::lock_guard<std::mutex> lk(m_listeners_lock);
-    m_listeners.erase(listener);
-}
-
-void LogContainer::Trigger(LogTypes::LOG_LEVELS level, const char *msg)
-{
-    std::lock_guard<std::mutex> lk(m_listeners_lock);
-
-    std::set<LogListener*>::const_iterator i;
-    for (i = m_listeners.begin(); i != m_listeners.end(); ++i)
-    {
-        (*i)->Log(level, msg);
-    }
-}
-
-FileLogListener::FileLogListener(const char *filename)
-{
-    OpenFStream(m_logfile, filename, std::ios::app);
-    SetEnable(true);
-}
-
-void FileLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
-{
-    if (!IsEnabled() || !IsValid())
-        return;
-
-    std::lock_guard<std::mutex> lk(m_log_lock);
-    m_logfile << msg << std::flush;
-}
-
-void DebuggerLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
-{
-#if _MSC_VER
-    ::OutputDebugStringA(msg);
-#endif
-}

+ 0 - 166
src/common/log_manager.h

@@ -1,166 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/log.h"
-#include "common/string_util.h"
-#include "common/file_util.h"
-
-#include <cstring>
-#include <set>
-#include <mutex>
-
-#define MAX_MESSAGES 8000
-#define MAX_MSGLEN  1024
-
-
-// pure virtual interface
-class LogListener
-{
-public:
-    virtual ~LogListener() {}
-
-    virtual void Log(LogTypes::LOG_LEVELS, const char *msg) = 0;
-};
-
-class FileLogListener : public LogListener
-{
-public:
-    FileLogListener(const char *filename);
-
-    void Log(LogTypes::LOG_LEVELS, const char *msg) override;
-
-    bool IsValid() { return !m_logfile.fail(); }
-    bool IsEnabled() const { return m_enable; }
-    void SetEnable(bool enable) { m_enable = enable; }
-
-    const char* GetName() const { return "file"; }
-
-private:
-    std::mutex m_log_lock;
-    std::ofstream m_logfile;
-    bool m_enable;
-};
-
-class DebuggerLogListener : public LogListener
-{
-public:
-    void Log(LogTypes::LOG_LEVELS, const char *msg) override;
-};
-
-class LogContainer
-{
-public:
-    LogContainer(const char* shortName, const char* fullName, bool enable = false);
-
-    const char* GetShortName() const { return m_shortName; }
-    const char* GetFullName() const { return m_fullName; }
-
-    void AddListener(LogListener* listener);
-    void RemoveListener(LogListener* listener);
-
-    void Trigger(LogTypes::LOG_LEVELS, const char *msg);
-
-    bool IsEnabled() const { return m_enable; }
-    void SetEnable(bool enable) { m_enable = enable; }
-
-    LogTypes::LOG_LEVELS GetLevel() const { return m_level;    }
-
-    void SetLevel(LogTypes::LOG_LEVELS level) {    m_level = level; }
-
-    bool HasListeners() const { return !m_listeners.empty(); }
-
-private:
-    char m_fullName[128];
-    char m_shortName[32];
-    bool m_enable;
-    LogTypes::LOG_LEVELS m_level;
-    std::mutex m_listeners_lock;
-    std::set<LogListener*> m_listeners;
-};
-
-class ConsoleListener;
-
-class LogManager : NonCopyable
-{
-private:
-    LogContainer* m_Log[LogTypes::NUMBER_OF_LOGS];
-    FileLogListener *m_fileLog;
-    ConsoleListener *m_consoleLog;
-    DebuggerLogListener *m_debuggerLog;
-    static LogManager *m_logManager;  // Singleton. Ugh.
-
-    LogManager();
-    ~LogManager();
-public:
-
-    static u32 GetMaxLevel() { return LogTypes::MAX_LOGLEVEL;    }
-
-    void Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
-        const char* function, const char *fmt, va_list args);
-
-    void SetLogLevel(LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS level)
-    {
-        m_Log[type]->SetLevel(level);
-    }
-
-    void SetEnable(LogTypes::LOG_TYPE type, bool enable)
-    {
-        m_Log[type]->SetEnable(enable);
-    }
-
-    bool IsEnabled(LogTypes::LOG_TYPE type) const
-    {
-        return m_Log[type]->IsEnabled();
-    }
-
-    const char* GetShortName(LogTypes::LOG_TYPE type) const
-    {
-        return m_Log[type]->GetShortName();
-    }
-
-    const char* GetFullName(LogTypes::LOG_TYPE type) const
-    {
-        return m_Log[type]->GetFullName();
-    }
-
-    void AddListener(LogTypes::LOG_TYPE type, LogListener *listener)
-    {
-        m_Log[type]->AddListener(listener);
-    }
-
-    void RemoveListener(LogTypes::LOG_TYPE type, LogListener *listener)
-    {
-        m_Log[type]->RemoveListener(listener);
-    }
-
-    FileLogListener *GetFileListener() const
-    {
-        return m_fileLog;
-    }
-
-    ConsoleListener *GetConsoleListener() const
-    {
-        return m_consoleLog;
-    }
-
-    DebuggerLogListener *GetDebuggerListener() const
-    {
-        return m_debuggerLog;
-    }
-
-    static LogManager* GetInstance()
-    {
-        return m_logManager;
-    }
-
-    static void SetInstance(LogManager *logManager)
-    {
-        m_logManager = logManager;
-    }
-
-    static void Init();
-    static void Shutdown();
-};

+ 151 - 0
src/common/logging/backend.cpp

@@ -0,0 +1,151 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/log.h" // For _dbg_assert_
+
+#include "common/logging/backend.h"
+#include "common/logging/log.h"
+#include "common/logging/text_formatter.h"
+
+namespace Log {
+
+static std::shared_ptr<Logger> global_logger;
+
+/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
+#define ALL_LOG_CLASSES() \
+        CLS(Log) \
+        CLS(Common) \
+        SUB(Common, Filesystem) \
+        SUB(Common, Memory) \
+        CLS(Core) \
+        SUB(Core, ARM11) \
+        CLS(Config) \
+        CLS(Debug) \
+        SUB(Debug, Emulated) \
+        SUB(Debug, GPU) \
+        SUB(Debug, Breakpoint) \
+        CLS(Kernel) \
+        SUB(Kernel, SVC) \
+        CLS(Service) \
+        SUB(Service, SRV) \
+        SUB(Service, FS) \
+        SUB(Service, APT) \
+        SUB(Service, GSP) \
+        SUB(Service, AC) \
+        SUB(Service, PTM) \
+        SUB(Service, CFG) \
+        SUB(Service, DSP) \
+        SUB(Service, HID) \
+        CLS(HW) \
+        SUB(HW, Memory) \
+        SUB(HW, GPU) \
+        CLS(Frontend) \
+        CLS(Render) \
+        SUB(Render, Software) \
+        SUB(Render, OpenGL) \
+        CLS(Loader)
+
+Logger::Logger() {
+    // Register logging classes so that they can be queried at runtime
+    size_t parent_class;
+    all_classes.reserve((size_t)Class::Count);
+
+#define CLS(x) \
+        all_classes.push_back(Class::x); \
+        parent_class = all_classes.size() - 1;
+#define SUB(x, y) \
+        all_classes.push_back(Class::x##_##y); \
+        all_classes[parent_class].num_children += 1;
+
+    ALL_LOG_CLASSES()
+#undef CLS
+#undef SUB
+
+    // Ensures that ALL_LOG_CLASSES isn't missing any entries.
+    _dbg_assert_(Log, all_classes.size() == (size_t)Class::Count);
+}
+
+// GetClassName is a macro defined by Windows.h, grrr...
+const char* Logger::GetLogClassName(Class log_class) {
+    switch (log_class) {
+#define CLS(x) case Class::x: return #x;
+#define SUB(x, y) case Class::x##_##y: return #x "." #y;
+        ALL_LOG_CLASSES()
+#undef CLS
+#undef SUB
+    }
+    return "Unknown";
+}
+
+const char* Logger::GetLevelName(Level log_level) {
+#define LVL(x) case Level::x: return #x
+    switch (log_level) {
+        LVL(Trace);
+        LVL(Debug);
+        LVL(Info);
+        LVL(Warning);
+        LVL(Error);
+        LVL(Critical);
+    }
+    return "Unknown";
+#undef LVL
+}
+
+void Logger::LogMessage(Entry entry) {
+    ring_buffer.Push(std::move(entry));
+}
+
+size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) {
+    return ring_buffer.BlockingPop(out_buffer, buffer_len);
+}
+
+std::shared_ptr<Logger> InitGlobalLogger() {
+    global_logger = std::make_shared<Logger>();
+    return global_logger;
+}
+
+Entry CreateEntry(Class log_class, Level log_level,
+                        const char* filename, unsigned int line_nr, const char* function,
+                        const char* format, va_list args) {
+    using std::chrono::steady_clock;
+    using std::chrono::duration_cast;
+
+    static steady_clock::time_point time_origin = steady_clock::now();
+
+    std::array<char, 4 * 1024> formatting_buffer;
+
+    Entry entry;
+    entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
+    entry.log_class = log_class;
+    entry.log_level = log_level;
+
+    snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr);
+    entry.location = std::string(formatting_buffer.data());
+
+    vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
+    entry.message = std::string(formatting_buffer.data());
+
+    return std::move(entry);
+}
+
+void LogMessage(Class log_class, Level log_level,
+                const char* filename, unsigned int line_nr, const char* function,
+                const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    Entry entry = CreateEntry(log_class, log_level,
+            filename, line_nr, function, format, args);
+    va_end(args);
+
+    if (global_logger != nullptr && !global_logger->IsClosed()) {
+        global_logger->LogMessage(std::move(entry));
+    } else {
+        // Fall back to directly printing to stderr
+        PrintMessage(entry);
+    }
+}
+
+}

+ 134 - 0
src/common/logging/backend.h

@@ -0,0 +1,134 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstdarg>
+#include <memory>
+#include <vector>
+
+#include "common/concurrent_ring_buffer.h"
+
+#include "common/logging/log.h"
+
+namespace Log {
+
+/**
+ * A log entry. Log entries are store in a structured format to permit more varied output
+ * formatting on different frontends, as well as facilitating filtering and aggregation.
+ */
+struct Entry {
+    std::chrono::microseconds timestamp;
+    Class log_class;
+    Level log_level;
+    std::string location;
+    std::string message;
+
+    Entry() = default;
+
+    // TODO(yuriks) Use defaulted move constructors once MSVC supports them
+#define MOVE(member) member(std::move(o.member))
+    Entry(Entry&& o)
+        : MOVE(timestamp), MOVE(log_class), MOVE(log_level),
+        MOVE(location), MOVE(message)
+    {}
+#undef MOVE
+
+    Entry& operator=(const Entry&& o) {
+#define MOVE(member) member = std::move(o.member)
+        MOVE(timestamp);
+        MOVE(log_class);
+        MOVE(log_level);
+        MOVE(location);
+        MOVE(message);
+#undef MOVE
+        return *this;
+    }
+};
+
+struct ClassInfo {
+    Class log_class;
+
+    /**
+        * Total number of (direct or indirect) sub classes this class has. If any, they follow in
+        * sequence after this class in the class list.
+        */
+    unsigned int num_children = 0;
+
+    ClassInfo(Class log_class) : log_class(log_class) {}
+};
+
+/**
+ * Logging management class. This class has the dual purpose of acting as an exchange point between
+ * the logging clients and the log outputter, as well as containing reflection info about available
+ * log classes.
+ */
+class Logger {
+private:
+    using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>;
+
+public:
+    static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED;
+
+    Logger();
+
+    /**
+     * Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of
+     * classes and subclasses, which together with the `num_children` field in ClassInfo, allows
+     * you to recover the hierarchy.
+     */
+    const std::vector<ClassInfo>& GetClasses() const { return all_classes; }
+
+    /**
+     * Returns the name of the passed log class as a C-string. Subclasses are separated by periods
+     * instead of underscores as in the enumeration.
+     */
+    static const char* GetLogClassName(Class log_class);
+
+    /**
+     * Returns the name of the passed log level as a C-string.
+     */
+    static const char* GetLevelName(Level log_level);
+
+    /**
+     * Appends a messages to the log buffer.
+     * @note This function is thread safe.
+     */
+    void LogMessage(Entry entry);
+
+    /**
+     * Retrieves a batch of messages from the log buffer, blocking until they are available.
+     * @note This function is thread safe.
+     *
+     * @param out_buffer Destination buffer that will receive the log entries.
+     * @param buffer_len The maximum size of `out_buffer`.
+     * @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is
+     *         returned, no entries are stored and the logger should shutdown.
+     */
+    size_t GetEntries(Entry* out_buffer, size_t buffer_len);
+
+    /**
+     * Initiates a shutdown of the logger. This will indicate to log output clients that they
+     * should shutdown.
+     */
+    void Close() { ring_buffer.Close(); }
+
+    /**
+     * Returns true if Close() has already been called on the Logger.
+     */
+    bool IsClosed() const { return ring_buffer.IsClosed(); }
+
+private:
+    Buffer ring_buffer;
+    std::vector<ClassInfo> all_classes;
+};
+
+/// Creates a log entry by formatting the given source location, and message.
+Entry CreateEntry(Class log_class, Level log_level,
+                        const char* filename, unsigned int line_nr, const char* function,
+                        const char* format, va_list args);
+/// Initializes the default Logger.
+std::shared_ptr<Logger> InitGlobalLogger();
+
+}

+ 132 - 0
src/common/logging/filter.cpp

@@ -0,0 +1,132 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/logging/filter.h"
+#include "common/logging/backend.h"
+#include "common/string_util.h"
+
+namespace Log {
+
+Filter::Filter(Level default_level) {
+    ResetAll(default_level);
+}
+
+void Filter::ResetAll(Level level) {
+    class_levels.fill(level);
+}
+
+void Filter::SetClassLevel(Class log_class, Level level) {
+    class_levels[static_cast<size_t>(log_class)] = level;
+}
+
+void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) {
+    const size_t log_class_i = static_cast<size_t>(log_class.log_class);
+
+    const size_t begin = log_class_i + 1;
+    const size_t end = begin + log_class.num_children;
+    for (size_t i = begin; begin < end; ++i) {
+        class_levels[i] = level;
+    }
+}
+
+void Filter::ParseFilterString(const std::string& filter_str) {
+    auto clause_begin = filter_str.cbegin();
+    while (clause_begin != filter_str.cend()) {
+        auto clause_end = std::find(clause_begin, filter_str.cend(), ' ');
+
+        // If clause isn't empty
+        if (clause_end != clause_begin) {
+            ParseFilterRule(clause_begin, clause_end);
+        }
+
+        if (clause_end != filter_str.cend()) {
+            // Skip over the whitespace
+            ++clause_end;
+        }
+        clause_begin = clause_end;
+    }
+}
+
+template <typename It>
+static Level GetLevelByName(const It begin, const It end) {
+    for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
+        const char* level_name = Logger::GetLevelName(static_cast<Level>(i));
+        if (Common::ComparePartialString(begin, end, level_name)) {
+            return static_cast<Level>(i);
+        }
+    }
+    return Level::Count;
+}
+
+template <typename It>
+static Class GetClassByName(const It begin, const It end) {
+    for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
+        const char* level_name = Logger::GetLogClassName(static_cast<Class>(i));
+        if (Common::ComparePartialString(begin, end, level_name)) {
+            return static_cast<Class>(i);
+        }
+    }
+    return Class::Count;
+}
+
+template <typename InputIt, typename T>
+static InputIt find_last(InputIt begin, const InputIt end, const T& value) {
+    auto match = end;
+    while (begin != end) {
+        auto new_match = std::find(begin, end, value);
+        if (new_match != end) {
+            match = new_match;
+            ++new_match;
+        }
+        begin = new_match;
+    }
+    return match;
+}
+
+bool Filter::ParseFilterRule(const std::string::const_iterator begin,
+        const std::string::const_iterator end) {
+    auto level_separator = std::find(begin, end, ':');
+    if (level_separator == end) {
+        LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
+                std::string(begin, end).c_str());
+        return false;
+    }
+
+    const Level level = GetLevelByName(level_separator + 1, end);
+    if (level == Level::Count) {
+        LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
+        return false;
+    }
+
+    if (Common::ComparePartialString(begin, level_separator, "*")) {
+        ResetAll(level);
+        return true;
+    }
+
+    auto class_name_end = find_last(begin, level_separator, '.');
+    if (class_name_end != level_separator &&
+            !Common::ComparePartialString(class_name_end + 1, level_separator, "*")) {
+        class_name_end = level_separator;
+    }
+
+    const Class log_class = GetClassByName(begin, class_name_end);
+    if (log_class == Class::Count) {
+        LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
+        return false;
+    }
+
+    if (class_name_end == level_separator) {
+        SetClassLevel(log_class, level);
+    }
+    SetSubclassesLevel(log_class, level);
+    return true;
+}
+
+bool Filter::CheckMessage(Class log_class, Level level) const {
+    return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
+}
+
+}

+ 63 - 0
src/common/logging/filter.h

@@ -0,0 +1,63 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <string>
+
+#include "common/logging/log.h"
+
+namespace Log {
+
+struct ClassInfo;
+
+/**
+ * Implements a log message filter which allows different log classes to have different minimum
+ * severity levels. The filter can be changed at runtime and can be parsed from a string to allow
+ * editing via the interface or loading from a configuration file.
+ */
+class Filter {
+public:
+    /// Initializes the filter with all classes having `default_level` as the minimum level.
+    Filter(Level default_level);
+
+    /// Resets the filter so that all classes have `level` as the minimum displayed level.
+    void ResetAll(Level level);
+    /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
+    void SetClassLevel(Class log_class, Level level);
+    /**
+     * Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class`
+     * itself is not changed.
+     */
+    void SetSubclassesLevel(const ClassInfo& log_class, Level level);
+
+    /**
+     * Parses a filter string and applies it to this filter.
+     *
+     * A filter string consists of a space-separated list of filter rules, each of the format
+     * `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
+     * A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and
+     * can be used to apply a rule to all classes or to all subclasses of a class without affecting
+     * the parent class. `<level>` a severity level name which will be set as the minimum logging
+     * level of the matched classes. Rules are applied left to right, with each rule overriding
+     * previous ones in the sequence.
+     *
+     * A few examples of filter rules:
+     *  - `*:Info` -- Resets the level of all classes to Info.
+     *  - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT,
+     *                       etc.) to Info.
+     *  - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the
+     *                         level of Service unchanged.
+     *  - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
+     */
+    void ParseFilterString(const std::string& filter_str);
+    bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end);
+
+    /// Matches class/level combination against the filter, returning true if it passed.
+    bool CheckMessage(Class log_class, Level level) const;
+
+private:
+    std::array<Level, (size_t)Class::Count> class_levels;
+};
+
+}

+ 115 - 0
src/common/logging/log.h

@@ -0,0 +1,115 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cassert>
+#include <chrono>
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Log {
+
+/// Specifies the severity or level of detail of the log message.
+enum class Level : u8 {
+    Trace,    ///< Extremely detailed and repetitive debugging information that is likely to
+              ///  pollute logs.
+    Debug,    ///< Less detailed debugging information.
+    Info,     ///< Status information from important points during execution.
+    Warning,  ///< Minor or potential problems found during execution of a task.
+    Error,    ///< Major problems found during execution of a task that prevent it from being
+              ///  completed.
+    Critical, ///< Major problems during execution that threathen the stability of the entire
+              ///  application.
+
+    Count ///< Total number of logging levels
+};
+
+typedef u8 ClassType;
+
+/**
+ * Specifies the sub-system that generated the log message.
+ *
+ * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp.
+ */
+enum class Class : ClassType {
+    Log,                        ///< Messages about the log system itself
+    Common,                     ///< Library routines
+    Common_Filesystem,          ///< Filesystem interface library
+    Common_Memory,              ///< Memory mapping and management functions
+    Core,                       ///< LLE emulation core
+    Core_ARM11,                 ///< ARM11 CPU core
+    Config,                     ///< Emulator configuration (including commandline)
+    Debug,                      ///< Debugging tools
+    Debug_Emulated,             ///< Debug messages from the emulated programs
+    Debug_GPU,                  ///< GPU debugging tools
+    Debug_Breakpoint,           ///< Logging breakpoints and watchpoints
+    Kernel,                     ///< The HLE implementation of the CTR kernel
+    Kernel_SVC,                 ///< Kernel system calls
+    Service,                    ///< HLE implementation of system services. Each major service
+                                ///  should have its own subclass.
+    Service_SRV,                ///< The SRV (Service Directory) implementation
+    Service_FS,                 ///< The FS (Filesystem) service implementation
+    Service_APT,                ///< The APT (Applets) service
+    Service_GSP,                ///< The GSP (GPU control) service
+    Service_AC,                 ///< The AC (WiFi status) service
+    Service_PTM,                ///< The PTM (Power status & misc.) service
+    Service_CFG,                ///< The CFG (Configuration) service
+    Service_DSP,                ///< The DSP (DSP control) service
+    Service_HID,                ///< The HID (User input) service
+    HW,                         ///< Low-level hardware emulation
+    HW_Memory,                  ///< Memory-map and address translation
+    HW_GPU,                     ///< GPU control emulation
+    Frontend,                   ///< Emulator UI
+    Render,                     ///< Emulator video output and hardware acceleration
+    Render_Software,            ///< Software renderer backend
+    Render_OpenGL,              ///< OpenGL backend
+    Loader,                     ///< ROM loader
+
+    Count ///< Total number of logging classes
+};
+
+/**
+ * Level below which messages are simply discarded without buffering regardless of the display
+ * settings.
+ */
+const Level MINIMUM_LEVEL =
+#ifdef _DEBUG
+    Level::Trace;
+#else
+    Level::Debug;
+#endif
+
+/**
+ * Logs a message to the global logger. This proxy exists to avoid exposing the details of the
+ * Logger class, including the ConcurrentRingBuffer template, to all files that desire to log
+ * messages, reducing unecessary recompilations.
+ */
+void LogMessage(Class log_class, Level log_level,
+    const char* filename, unsigned int line_nr, const char* function,
+#ifdef _MSC_VER
+    _Printf_format_string_
+#endif
+    const char* format, ...)
+#ifdef __GNUC__
+    __attribute__((format(printf, 6, 7)))
+#endif
+    ;
+
+} // namespace Log
+
+#define LOG_GENERIC(log_class, log_level, ...) \
+    do { \
+        if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \
+            ::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \
+                       __FILE__, __LINE__, __func__, __VA_ARGS__); \
+    } while (0)
+
+#define LOG_TRACE(   log_class, ...) LOG_GENERIC(log_class, Trace,    __VA_ARGS__)
+#define LOG_DEBUG(   log_class, ...) LOG_GENERIC(log_class, Debug,    __VA_ARGS__)
+#define LOG_INFO(    log_class, ...) LOG_GENERIC(log_class, Info,     __VA_ARGS__)
+#define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning,  __VA_ARGS__)
+#define LOG_ERROR(   log_class, ...) LOG_GENERIC(log_class, Error,    __VA_ARGS__)
+#define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__)

+ 136 - 0
src/common/logging/text_formatter.cpp

@@ -0,0 +1,136 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstdio>
+
+#ifdef _WIN32
+#   define WIN32_LEAN_AND_MEAN
+#   include <Windows.h>
+#endif
+
+#include "common/logging/backend.h"
+#include "common/logging/filter.h"
+#include "common/logging/log.h"
+#include "common/logging/text_formatter.h"
+
+#include "common/string_util.h"
+
+namespace Log {
+
+// TODO(bunnei): This should be moved to a generic path manipulation library
+const char* TrimSourcePath(const char* path, const char* root) {
+    const char* p = path;
+
+    while (*p != '\0') {
+        const char* next_slash = p;
+        while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
+            ++next_slash;
+        }
+
+        bool is_src = Common::ComparePartialString(p, next_slash, root);
+        p = next_slash;
+
+        if (*p != '\0') {
+            ++p;
+        }
+        if (is_src) {
+            path = p;
+        }
+    }
+    return path;
+}
+
+void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) {
+    unsigned int time_seconds    = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
+    unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
+
+    const char* class_name = Logger::GetLogClassName(entry.log_class);
+    const char* level_name = Logger::GetLevelName(entry.log_level);
+
+    snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s",
+        time_seconds, time_fractional, class_name, level_name,
+        TrimSourcePath(entry.location.c_str()), entry.message.c_str());
+}
+
+void PrintMessage(const Entry& entry) {
+    std::array<char, 4 * 1024> format_buffer;
+    FormatLogMessage(entry, format_buffer.data(), format_buffer.size());
+    fputs(format_buffer.data(), stderr);
+    fputc('\n', stderr);
+}
+
+void PrintColoredMessage(const Entry& entry) {
+#ifdef _WIN32
+    static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
+
+    CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
+    GetConsoleScreenBufferInfo(console_handle, &original_info);
+
+    WORD color = 0;
+    switch (entry.log_level) {
+    case Level::Trace: // Grey
+        color = FOREGROUND_INTENSITY; break;
+    case Level::Debug: // Cyan
+        color = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
+    case Level::Info: // Bright gray
+        color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
+    case Level::Warning: // Bright yellow
+        color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
+    case Level::Error: // Bright red
+        color = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
+    case Level::Critical: // Bright magenta
+        color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
+    }
+
+    SetConsoleTextAttribute(console_handle, color);
+#else
+#   define ESC "\x1b"
+    const char* color = "";
+    switch (entry.log_level) {
+    case Level::Trace: // Grey
+        color = ESC "[1;30m"; break;
+    case Level::Debug: // Cyan
+        color = ESC "[0;36m"; break;
+    case Level::Info: // Bright gray
+        color = ESC "[0;37m"; break;
+    case Level::Warning: // Bright yellow
+        color = ESC "[1;33m"; break;
+    case Level::Error: // Bright red
+        color = ESC "[1;31m"; break;
+    case Level::Critical: // Bright magenta
+        color = ESC "[1;35m"; break;
+    }
+
+    fputs(color, stderr);
+#endif
+
+    PrintMessage(entry);
+
+#ifdef _WIN32
+    SetConsoleTextAttribute(console_handle, original_info.wAttributes);
+#else
+    fputs(ESC "[0m", stderr);
+#   undef ESC
+#endif
+}
+
+void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) {
+    std::array<Entry, 256> entry_buffer;
+
+    while (true) {
+        size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size());
+        if (num_entries == Logger::QUEUE_CLOSED) {
+            break;
+        }
+        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);
+            }
+        }
+    }
+}
+
+}

+ 41 - 0
src/common/logging/text_formatter.h

@@ -0,0 +1,41 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+
+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
+ * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
+ * leaving only the path relative to the sources root.
+ *
+ * @param path The input file path as a null-terminated string
+ * @param root The name of the root source directory as a null-terminated string. Path up to and
+ *             including the last occurence of this name will be stripped
+ * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
+ */
+const char* TrimSourcePath(const char* path, const char* root = "src");
+
+/// Formats a log entry into the provided text buffer.
+void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
+/// Formats and prints a log entry to stderr.
+void PrintMessage(const Entry& entry);
+/// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
+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);
+
+}

+ 16 - 0
src/common/make_unique.h

@@ -0,0 +1,16 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+namespace Common {
+
+template <typename T, typename... Args>
+std::unique_ptr<T> make_unique(Args&&... args) {
+    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+} // namespace

+ 2 - 2
src/common/math_util.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 

+ 2 - 2
src/common/math_util.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 17 - 17
src/common/mem_arena.cpp

@@ -30,7 +30,7 @@
 #endif
 
 #ifdef IOS
-void* globalbase = NULL;
+void* globalbase = nullptr;
 #endif
 
 #ifdef ANDROID
@@ -71,7 +71,7 @@ int ashmem_create_region(const char *name, size_t size)
     return fd;
 
 error:
-    ERROR_LOG(MEMMAP, "NASTY ASHMEM ERROR: ret = %08x", ret);
+    LOG_ERROR(Common_Memory, "NASTY ASHMEM ERROR: ret = %08x", ret);
     close(fd);
     return ret;
 }
@@ -121,7 +121,7 @@ void MemArena::GrabLowMemSpace(size_t size)
 {
 #ifdef _WIN32
 #ifndef _XBOX
-    hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)(size), NULL);
+    hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, (DWORD)(size), nullptr);
     GetSystemInfo(&sysInfo);
 #endif
 #elif defined(ANDROID)
@@ -130,7 +130,7 @@ void MemArena::GrabLowMemSpace(size_t size)
     // Note that it appears that ashmem is pinned by default, so no need to pin.
     if (fd < 0)
     {
-        ERROR_LOG(MEMMAP, "Failed to grab ashmem space of size: %08x  errno: %d", (int)size, (int)(errno));
+        LOG_ERROR(Common_Memory, "Failed to grab ashmem space of size: %08x  errno: %d", (int)size, (int)(errno));
         return;
     }
 #else
@@ -148,12 +148,12 @@ void MemArena::GrabLowMemSpace(size_t size)
         }
         else if (errno != EEXIST)
         {
-            ERROR_LOG(MEMMAP, "shm_open failed: %s", strerror(errno));
+            LOG_ERROR(Common_Memory, "shm_open failed: %s", strerror(errno));
             return;
         }
     }
     if (ftruncate(fd, size) < 0)
-        ERROR_LOG(MEMMAP, "Failed to allocate low memory space");
+        LOG_ERROR(Common_Memory, "Failed to allocate low memory space");
 #endif
 }
 
@@ -178,7 +178,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
 #ifdef _XBOX
     size = roundup(size);
     // use 64kb pages
-    void * ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
+    void * ptr = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
     return ptr;
 #else
     size = roundup(size);
@@ -197,7 +197,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
 
     if (retval == MAP_FAILED)
     {
-        NOTICE_LOG(MEMMAP, "mmap failed");
+        LOG_ERROR(Common_Memory, "mmap failed");
         return nullptr;
     }
     return retval;
@@ -243,8 +243,8 @@ u8* MemArena::Find4GBBase()
     return base;
 #else
 #ifdef IOS
-    void* base = NULL;
-    if (globalbase == NULL){
+    void* base = nullptr;
+    if (globalbase == nullptr){
         base = mmap(0, 0x08000000, PROT_READ | PROT_WRITE,
             MAP_ANON | MAP_SHARED, -1, 0);
         if (base == MAP_FAILED) {
@@ -357,7 +357,7 @@ bail:
         if (views[j].out_ptr_low && *views[j].out_ptr_low)
         {
             arena->ReleaseView(*views[j].out_ptr_low, views[j].size);
-            *views[j].out_ptr_low = NULL;
+            *views[j].out_ptr_low = nullptr;
         }
         if (*views[j].out_ptr)
         {
@@ -369,7 +369,7 @@ bail:
                 arena->ReleaseView(*views[j].out_ptr, views[j].size);
             }
 #endif
-            *views[j].out_ptr = NULL;
+            *views[j].out_ptr = nullptr;
         }
     }
     return false;
@@ -415,7 +415,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena
 #elif defined(_WIN32)
     // Try a whole range of possible bases. Return once we got a valid one.
     u32 max_base_addr = 0x7FFF0000 - 0x10000000;
-    u8 *base = NULL;
+    u8 *base = nullptr;
 
     for (u32 base_addr = 0x01000000; base_addr < max_base_addr; base_addr += 0x400000)
     {
@@ -423,7 +423,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena
         base = (u8 *)base_addr;
         if (Memory_TryBase(base, views, num_views, flags, arena))
         {
-            INFO_LOG(MEMMAP, "Found valid memory base at %p after %i tries.", base, base_attempts);
+            LOG_DEBUG(Common_Memory, "Found valid memory base at %p after %i tries.", base, base_attempts);
             base_attempts = 0;
             break;
         }
@@ -442,7 +442,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena
     u8 *base = MemArena::Find4GBBase();
     if (!Memory_TryBase(base, views, num_views, flags, arena))
     {
-        ERROR_LOG(MEMMAP, "MemoryMap_Setup: Failed finding a memory base.");
+        LOG_ERROR(Common_Memory, "MemoryMap_Setup: Failed finding a memory base.");
         PanicAlert("MemoryMap_Setup: Failed finding a memory base.");
         return 0;
     }
@@ -463,8 +463,8 @@ void MemoryMap_Shutdown(const MemoryView *views, int num_views, u32 flags, MemAr
             arena->ReleaseView(*views[i].out_ptr_low, views[i].size);
         if (*views[i].out_ptr && (views[i].out_ptr_low && *views[i].out_ptr != *views[i].out_ptr_low))
             arena->ReleaseView(*views[i].out_ptr, views[i].size);
-        *views[i].out_ptr = NULL;
+        *views[i].out_ptr = nullptr;
         if (views[i].out_ptr_low)
-            *views[i].out_ptr_low = NULL;
+            *views[i].out_ptr_low = nullptr;
     }
 }

+ 8 - 8
src/common/memory_util.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 
@@ -93,7 +93,7 @@ void* AllocateMemoryPages(size_t size)
     // printf("Mapped memory at %p (size %ld)\n", ptr,
     //    (unsigned long)size);
 
-    if (ptr == NULL)
+    if (ptr == nullptr)
         PanicAlert("Failed to allocate raw memory");
 
     return ptr;
@@ -104,19 +104,19 @@ void* AllocateAlignedMemory(size_t size,size_t alignment)
 #ifdef _WIN32
     void* ptr =  _aligned_malloc(size,alignment);
 #else
-    void* ptr = NULL;
+    void* ptr = nullptr;
 #ifdef ANDROID
     ptr = memalign(alignment, size);
 #else
     if (posix_memalign(&ptr, alignment, size) != 0)
-        ERROR_LOG(MEMMAP, "Failed to allocate aligned memory");
+        LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
 #endif
 #endif
 
     // printf("Mapped memory at %p (size %ld)\n", ptr,
     //    (unsigned long)size);
 
-    if (ptr == NULL)
+    if (ptr == nullptr)
         PanicAlert("Failed to allocate aligned memory");
 
     return ptr;
@@ -130,7 +130,7 @@ void FreeMemoryPages(void* ptr, size_t size)
 
         if (!VirtualFree(ptr, 0, MEM_RELEASE))
             PanicAlert("FreeMemoryPages failed!\n%s", GetLastErrorMsg());
-        ptr = NULL; // Is this our responsibility?
+        ptr = nullptr; // Is this our responsibility?
 
 #else
         munmap(ptr, size);
@@ -184,7 +184,7 @@ std::string MemUsage()
     // Print information about the memory usage of the process.
 
     hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
-    if (NULL == hProcess) return "MemUsage Error";
+    if (nullptr == hProcess) return "MemUsage Error";
 
     if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
         Ret = Common::StringFromFormat("%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str());

+ 2 - 2
src/common/memory_util.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 4 - 4
src/common/misc.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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"
@@ -23,9 +23,9 @@ const char* GetLastErrorMsg()
 #ifdef _WIN32
     static __declspec(thread) char err_str[buff_size] = {};
 
-    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
+    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(),
         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-        err_str, buff_size, NULL);
+        err_str, buff_size, nullptr);
 #else
     static __thread char err_str[buff_size] = {};
 

+ 3 - 3
src/common/msg_handler.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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>
@@ -75,7 +75,7 @@ bool MsgAlert(bool yes_no, int Style, const char* format, ...)
     Common::CharArrayFromFormatV(buffer, sizeof(buffer)-1, str_translator(format).c_str(), args);
     va_end(args);
 
-    ERROR_LOG(MASTER_LOG, "%s: %s", caption.c_str(), buffer);
+    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))

+ 2 - 2
src/common/msg_handler.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 1 - 1
src/common/platform.h

@@ -80,7 +80,7 @@
 inline struct tm* localtime_r(const time_t *clock, struct tm *result) {
     if (localtime_s(result, clock) == 0)
         return result;
-    return NULL;
+    return nullptr;
 }
 #endif
 

+ 1 - 1
src/common/scm_rev.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 37 - 0
src/common/scope_exit.h

@@ -0,0 +1,37 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace detail {
+    template <typename Func>
+    struct ScopeExitHelper {
+        explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
+        ~ScopeExitHelper() { func(); }
+
+        Func func;
+    };
+
+    template <typename Func>
+    ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); }
+}
+
+/**
+ * This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
+ * for doing ad-hoc clean-up tasks in a function with multiple returns.
+ *
+ * Example usage:
+ * \code
+ * const int saved_val = g_foo;
+ * g_foo = 55;
+ * SCOPE_EXIT({ g_foo = saved_val; });
+ *
+ * if (Bar()) {
+ *     return 0;
+ * } else {
+ *     return 20;
+ * }
+ * \endcode
+ */
+#define SCOPE_EXIT(body) auto scope_exit_helper_##__LINE__ = detail::ScopeExit([&]() body)

+ 16 - 16
src/common/string_util.cpp

@@ -1,8 +1,8 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <algorithm>
+#include <boost/range/algorithm.hpp>
 
 #include "common/common.h"
 #include "common/string_util.h"
@@ -18,20 +18,20 @@ namespace Common {
 
 /// Make a string lowercase
 std::string ToLower(std::string str) {
-    std::transform(str.begin(), str.end(), str.begin(), ::tolower);
+    boost::transform(str, str.begin(), ::tolower);
     return str;
 }
 
 /// Make a string uppercase
 std::string ToUpper(std::string str) {
-    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
+    boost::transform(str, str.begin(), ::toupper);
     return str;
 }
 
 // faster than sscanf
 bool AsciiToHex(const char* _szValue, u32& result)
 {
-    char *endptr = NULL;
+    char *endptr = nullptr;
     const u32 value = strtoul(_szValue, &endptr, 16);
 
     if (!endptr || *endptr)
@@ -69,7 +69,7 @@ bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list ar
     // will be present in the middle of a multibyte sequence.
     //
     // This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l.
-    static locale_t c_locale = NULL;
+    static locale_t c_locale = nullptr;
     if (!c_locale)
         c_locale = _create_locale(LC_ALL, ".1252");
     writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args);
@@ -92,7 +92,7 @@ bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list ar
 std::string StringFromFormat(const char* format, ...)
 {
     va_list args;
-    char *buf = NULL;
+    char *buf = nullptr;
 #ifdef _WIN32
     int required = 0;
 
@@ -107,7 +107,7 @@ std::string StringFromFormat(const char* format, ...)
 #else
     va_start(args, format);
     if (vasprintf(&buf, format, args) < 0)
-        ERROR_LOG(COMMON, "Unable to allocate memory for string");
+        LOG_ERROR(Common, "Unable to allocate memory for string");
     va_end(args);
 
     std::string temp = buf;
@@ -162,7 +162,7 @@ std::string StripQuotes(const std::string& s)
 
 bool TryParse(const std::string &str, u32 *const output)
 {
-    char *endptr = NULL;
+    char *endptr = nullptr;
 
     // Reset errno to a value other than ERANGE
     errno = 0;
@@ -475,7 +475,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
     iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
     if ((iconv_t)(-1) == conv_desc)
     {
-        ERROR_LOG(COMMON, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
+        LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
         iconv_close(conv_desc);
         return {};
     }
@@ -510,7 +510,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
             }
             else
             {
-                ERROR_LOG(COMMON, "iconv failure [%s]: %s", fromcode, strerror(errno));
+                LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno));
                 break;
             }
         }
@@ -528,10 +528,10 @@ std::u16string UTF8ToUTF16(const std::string& input)
 {
     std::u16string result;
 
-    iconv_t const conv_desc = iconv_open("UTF-16", "UTF-8");
+    iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
     if ((iconv_t)(-1) == conv_desc)
     {
-        ERROR_LOG(COMMON, "Iconv initialization failure [UTF-8]: %s", strerror(errno));
+        LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno));
         iconv_close(conv_desc);
         return {};
     }
@@ -566,7 +566,7 @@ std::u16string UTF8ToUTF16(const std::string& input)
             }
             else
             {
-                ERROR_LOG(COMMON, "iconv failure [UTF-8]: %s", strerror(errno));
+                LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno));
                 break;
             }
         }
@@ -582,7 +582,7 @@ std::u16string UTF8ToUTF16(const std::string& input)
 
 std::string UTF16ToUTF8(const std::u16string& input)
 {
-    return CodeToUTF8("UTF-16", input);
+    return CodeToUTF8("UTF-16LE", input);
 }
 
 std::string CP1252ToUTF8(const std::string& input)

+ 17 - 2
src/common/string_util.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
@@ -115,4 +115,19 @@ inline std::string UTF8ToTStr(const std::string& str)
 
 #endif
 
+/**
+ * Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string
+ * `other` for equality.
+ */
+template <typename InIt>
+bool ComparePartialString(InIt begin, InIt end, const char* other) {
+    for (; begin != end && *other != '\0'; ++begin, ++other) {
+        if (*begin != *other) {
+            return false;
+        }
+    }
+    // Only return true if both strings finished at the same point
+    return (begin == end) == (*other == '\0');
+}
+
 }

+ 1 - 1
src/common/symbols.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include "common/symbols.h"

+ 1 - 1
src/common/symbols.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

+ 2 - 2
src/common/thread.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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/thread.h"

+ 3 - 2
src/common/thread.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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
@@ -21,6 +21,7 @@
 //for gettimeofday and struct time(spec|val)
 #include <time.h>
 #include <sys/time.h>
+#include <unistd.h>
 #endif
 
 namespace Common

+ 7 - 7
src/common/thread_queue_list.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project / PPSSPP Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -37,7 +37,7 @@ struct ThreadQueueList {
     ~ThreadQueueList() {
         for (int i = 0; i < NUM_QUEUES; ++i)
         {
-            if (queues[i].data != NULL)
+            if (queues[i].data != nullptr)
                 free(queues[i].data);
         }
     }
@@ -46,7 +46,7 @@ struct ThreadQueueList {
     int contains(const IdType uid) {
         for (int i = 0; i < NUM_QUEUES; ++i)
         {
-            if (queues[i].data == NULL)
+            if (queues[i].data == nullptr)
                 continue;
 
             Queue *cur = &queues[i];
@@ -133,7 +133,7 @@ struct ThreadQueueList {
     inline void clear() {
         for (int i = 0; i < NUM_QUEUES; ++i)
         {
-            if (queues[i].data != NULL)
+            if (queues[i].data != nullptr)
                 free(queues[i].data);
         }
         memset(queues, 0, sizeof(queues));
@@ -147,7 +147,7 @@ struct ThreadQueueList {
 
     inline void prepare(u32 priority) {
         Queue *cur = &queues[priority];
-        if (cur->next == NULL)
+        if (cur->next == nullptr)
             link(priority, INITIAL_CAPACITY);
     }
 
@@ -176,7 +176,7 @@ private:
 
         for (int i = (int) priority - 1; i >= 0; --i)
         {
-            if (queues[i].next != NULL)
+            if (queues[i].next != nullptr)
             {
                 cur->next = queues[i].next;
                 queues[i].next = cur;
@@ -193,7 +193,7 @@ private:
         int size = cur->end - cur->first;
         if (size >= cur->capacity - 2)  {
             IdType *new_data = (IdType *)realloc(cur->data, cur->capacity * 2 * sizeof(IdType));
-            if (new_data != NULL)  {
+            if (new_data != nullptr)  {
                 cur->capacity *= 2;
                 cur->data = new_data;
             }

+ 2 - 2
src/common/thunk.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 5 - 5
src/common/timer.cpp

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include <time.h>
@@ -25,7 +25,7 @@ u32 Timer::GetTimeMs()
     return timeGetTime();
 #else
     struct timeval t;
-    (void)gettimeofday(&t, NULL);
+    (void)gettimeofday(&t, nullptr);
     return ((u32)(t.tv_sec * 1000 + t.tv_usec / 1000));
 #endif
 }
@@ -183,7 +183,7 @@ std::string Timer::GetTimeFormatted()
     return StringFromFormat("%s:%03i", tmp, tp.millitm);
 #else
     struct timeval t;
-    (void)gettimeofday(&t, NULL);
+    (void)gettimeofday(&t, nullptr);
     return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000));
 #endif
 }
@@ -197,7 +197,7 @@ double Timer::GetDoubleTime()
     (void)::ftime(&tp);
 #else
     struct timeval t;
-    (void)gettimeofday(&t, NULL);
+    (void)gettimeofday(&t, nullptr);
 #endif
     // Get continuous timestamp
     u64 TmpSeconds = Common::Timer::GetTimeSinceJan1970();

+ 2 - 2
src/common/timer.h

@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// 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

+ 12 - 12
src/common/utf8.cpp

@@ -281,28 +281,28 @@ int u8_read_escape_sequence(const char *str, u32 *dest)
     do {
       digs[dno++] = str[i++];
     } while (octal_digit(str[i]) && dno < 3);
-    ch = strtol(digs, NULL, 8);
+    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, NULL, 16);
+      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, NULL, 16);
+      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, NULL, 16);
+      ch = strtol(digs, nullptr, 16);
   }
   *dest = ch;
 
@@ -353,7 +353,7 @@ const char *u8_strchr(const char *s, u32 ch, int *charn)
     lasti = i;
     (*charn)++;
   }
-  return NULL;
+  return nullptr;
 }
 
 const char *u8_memchr(const char *s, u32 ch, size_t sz, int *charn)
@@ -378,7 +378,7 @@ const char *u8_memchr(const char *s, u32 ch, size_t sz, int *charn)
     lasti = i;
     (*charn)++;
   }
-  return NULL;
+  return nullptr;
 }
 
 int u8_is_locale_utf8(const char *locale)
@@ -419,35 +419,35 @@ bool UTF8StringHasNonASCII(const char *utf8string) {
 
 std::string ConvertWStringToUTF8(const wchar_t *wstr) {
     int len = (int)wcslen(wstr);
-    int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr, len, 0, 0, NULL, NULL);
+    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, NULL, NULL);
+        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, NULL, NULL);
+    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, NULL, NULL);
+        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, NULL, 0);
+    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, NULL, 0);
+    int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, nullptr, 0);
     std::wstring str;
     str.resize(size);
     if (size > 0) {

+ 40 - 17
src/core/CMakeLists.txt

@@ -18,35 +18,46 @@ set(SRCS
             arm/skyeye_common/vfp/vfpinstr.cpp
             arm/skyeye_common/vfp/vfpsingle.cpp
             file_sys/archive_romfs.cpp
+            file_sys/archive_savedata.cpp
             file_sys/archive_sdmc.cpp
+            file_sys/archive_systemsavedata.cpp
+            file_sys/disk_archive.cpp
             file_sys/file_romfs.cpp
-            file_sys/file_sdmc.cpp
             file_sys/directory_romfs.cpp
-            file_sys/directory_sdmc.cpp
             hle/kernel/address_arbiter.cpp
-            hle/kernel/archive.cpp
             hle/kernel/event.cpp
             hle/kernel/kernel.cpp
             hle/kernel/mutex.cpp
+            hle/kernel/semaphore.cpp
             hle/kernel/shared_memory.cpp
             hle/kernel/thread.cpp
             hle/service/ac_u.cpp
+            hle/service/act_u.cpp
+            hle/service/am_app.cpp
             hle/service/am_net.cpp
+            hle/service/apt_a.cpp
             hle/service/apt_u.cpp
             hle/service/boss_u.cpp
-            hle/service/cfg_i.cpp
-            hle/service/cfg_u.cpp
+            hle/service/cecd_u.cpp
+            hle/service/cfg/cfg.cpp
+            hle/service/cfg/cfg_i.cpp
+            hle/service/cfg/cfg_u.cpp
             hle/service/csnd_snd.cpp
             hle/service/dsp_dsp.cpp
             hle/service/err_f.cpp
-            hle/service/fs_user.cpp
             hle/service/frd_u.cpp
+            hle/service/fs/archive.cpp
+            hle/service/fs/fs_user.cpp
             hle/service/gsp_gpu.cpp
             hle/service/hid_user.cpp
+            hle/service/http_c.cpp
             hle/service/ir_rst.cpp
             hle/service/ir_u.cpp
+            hle/service/ldr_ro.cpp
             hle/service/mic_u.cpp
             hle/service/ndm_u.cpp
+            hle/service/news_u.cpp
+            hle/service/nim_aoc.cpp
             hle/service/nwm_uds.cpp
             hle/service/pm_app.cpp
             hle/service/ptm_u.cpp
@@ -59,10 +70,10 @@ set(SRCS
             hle/svc.cpp
             hw/gpu.cpp
             hw/hw.cpp
-            hw/ndma.cpp
             loader/elf.cpp
             loader/loader.cpp
             loader/ncch.cpp
+            loader/3dsx.cpp
             core.cpp
             core_timing.cpp
             mem_map.cpp
@@ -92,39 +103,51 @@ set(HEADERS
             arm/skyeye_common/vfp/vfp.h
             arm/skyeye_common/vfp/vfp_helper.h
             arm/arm_interface.h
-            file_sys/archive.h
+            file_sys/archive_backend.h
             file_sys/archive_romfs.h
+            file_sys/archive_savedata.h
             file_sys/archive_sdmc.h
-            file_sys/file.h
+            file_sys/archive_systemsavedata.h
+            file_sys/disk_archive.h
+            file_sys/file_backend.h
             file_sys/file_romfs.h
-            file_sys/file_sdmc.h
-            file_sys/directory.h
+            file_sys/directory_backend.h
             file_sys/directory_romfs.h
-            file_sys/directory_sdmc.h
             hle/kernel/address_arbiter.h
-            hle/kernel/archive.h
             hle/kernel/event.h
             hle/kernel/kernel.h
             hle/kernel/mutex.h
+            hle/kernel/semaphore.h
+            hle/kernel/session.h
             hle/kernel/shared_memory.h
             hle/kernel/thread.h
             hle/service/ac_u.h
+            hle/service/act_u.h
+            hle/service/am_app.h
             hle/service/am_net.h
+            hle/service/apt_a.h
             hle/service/apt_u.h
             hle/service/boss_u.h
-            hle/service/cfg_i.h
-            hle/service/cfg_u.h
+            hle/service/cecd_u.h
+            hle/service/cfg/cfg.h
+            hle/service/cfg/cfg_i.h
+            hle/service/cfg/cfg_u.h
             hle/service/csnd_snd.h
             hle/service/dsp_dsp.h
             hle/service/err_f.h
-            hle/service/fs_user.h
             hle/service/frd_u.h
+            hle/service/fs/archive.h
+            hle/service/fs/fs_user.h
             hle/service/gsp_gpu.h
             hle/service/hid_user.h
+            hle/service/http_c.h
             hle/service/ir_rst.h
             hle/service/ir_u.h
+            hle/service/ldr_ro.h
             hle/service/mic_u.h
             hle/service/ndm_u.h
+            hle/service/news_u.h
+            hle/service/nim_aoc.h
             hle/service/nwm_uds.h
             hle/service/pm_app.h
             hle/service/ptm_u.h
@@ -139,10 +162,10 @@ set(HEADERS
             hle/svc.h
             hw/gpu.h
             hw/hw.h
-            hw/ndma.h
             loader/elf.h
             loader/loader.h
             loader/ncch.h
+            loader/3dsx.h
             core.h
             core_timing.h
             mem_map.h

+ 7 - 1
src/core/arm/arm_interface.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
@@ -77,6 +77,12 @@ public:
      */
     virtual u64 GetTicks() const = 0;
 
+    /**
+     * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time)
+     * @param ticks Number of ticks to advance the CPU core
+     */
+    virtual void AddTicks(u64 ticks) = 0;
+
     /**
      * Saves the current CPU context
      * @param ctx Thread context to save

+ 1 - 1
src/core/arm/disassembler/load_symbol_map.cpp

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #include <string>

+ 1 - 1
src/core/arm/disassembler/load_symbol_map.h

@@ -1,5 +1,5 @@
 // Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini