diff options
170 files changed, 4101 insertions, 7732 deletions
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index feba3fd6e..155d8a5c8 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -18,19 +18,20 @@ cd ..  mkdir package  if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then -  QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/' +  QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins'  else    #fallback to qt -  QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/' +  QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins'  fi  find build/ -name "yuzu*.exe" -exec cp {} 'package' \;  # copy Qt plugins  mkdir package/platforms -cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/ -cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/ -cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/ +cp -v "${QT_PLUGINS_PATH}/platforms/qwindows.dll" package/platforms/ +cp -rv "${QT_PLUGINS_PATH}/mediaservice/" package/ +cp -rv "${QT_PLUGINS_PATH}/imageformats/" package/ +cp -rv "${QT_PLUGINS_PATH}/styles/" package/  rm -f package/mediaservice/*d.dll  for i in package/*.exe; do diff --git a/.gitmodules b/.gitmodules index 8e5bc4581..749cd0408 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,7 +18,7 @@  	url = https://github.com/libusb/libusb.git  [submodule "discord-rpc"]      path = externals/discord-rpc -    url = https://github.com/discordapp/discord-rpc.git +    url = https://github.com/discord/discord-rpc.git  [submodule "Vulkan-Headers"]      path = externals/Vulkan-Headers      url = https://github.com/KhronosGroup/Vulkan-Headers.git @@ -43,3 +43,6 @@  [submodule "SDL"]  	path = externals/SDL  	url = https://github.com/libsdl-org/SDL.git +[submodule "externals/cpp-httplib"] +	path = externals/cpp-httplib +	url = https://github.com/yhirose/cpp-httplib.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c2ab2330..8b1734f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,7 +172,7 @@ macro(yuzu_find_packages)      set(REQUIRED_LIBS      #    Cmake Pkg Prefix  Version     Conan Pkg          "Catch2            2.13        catch2/2.13.0" -        "fmt               7.1         fmt/7.1.2" +        "fmt               8.0         fmt/8.0.0"          "lz4               1.8         lz4/1.9.2"          "nlohmann_json     3.8         nlohmann_json/3.8.0"          "ZLIB              1.2         zlib/1.2.11" @@ -1,43 +1,80 @@ -yuzu emulator -============= -[](https://travis-ci.com/yuzu-emu/yuzu) -[](https://dev.azure.com/yuzu-emu/yuzu/) -[](https://discord.com/invite/u77vRWY) +<h1 align="center"> +  <br> +  <a href="https://yuzu-emu.org/"><img src="https://raw.githubusercontent.com/yuzu-emu/yuzu-assets/master/icons/icon.png" alt="yuzu" width="200"></a> +  <br> +  <b>yuzu</b> +  <br> +</h1> -yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/). +<h4 align="center"><b>yuzu</b> is the world's most popular, open-source, Nintendo Switch emulator — started by the creators of <a href="https://citra-emu.org" target="_blank">Citra</a>. +<br> +It is written in C++ with portability in mind, and we actively maintain builds for Windows and Linux. +</h4> -It is written in C++ with portability in mind, with builds actively maintained for Windows and Linux. The emulator is capable of running several commercial games. +<p align="center"> +    <a href="https://dev.azure.com/yuzu-emu/yuzu/"> +        <img src="https://dev.azure.com/yuzu-emu/yuzu/_apis/build/status/yuzu%20mainline?branchName=master" +            alt="Azure Mainline CI Build Status"> +    </a> +    <a href="https://discord.com/invite/u77vRWY"> +        <img src="https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white" +            alt="Discord"> +    </a> +</p> -yuzu only emulates a subset of Switch hardware and therefore most commercial games **do not** run at full speed or are not fully functional. +<p align="center"> +  <a href="#compatibility">Compatibility</a> | +  <a href="#development">Development</a> | +  <a href="#building">Building</a> | +  <a href="#download">Download</a> | +  <a href="#support">Support</a> | +  <a href="#license">License</a> +</p> -Do you want to check which games are compatible and which ones are not? Please visit our [Compatibility page](https://yuzu-emu.org/game/)! +## Compatibility -yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. +The emulator is capable of running most commercial games at full speed, provided you meet the [necessary hardware requirements](https://yuzu-emu.org/help/quickstart/#hardware-requirements). -Check out our [website](https://yuzu-emu.org/)! +For a full list of games yuzu support, please visit our [Compatibility page](https://yuzu-emu.org/game/)  -For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY). +Check out our [website](https://yuzu-emu.org/) for the latest news on exciting features, monthly progress reports, and more! -### Development +## Development -Most of the development happens on GitHub. It's also where [our central repository](https://github.com/yuzu-emu/yuzu) is hosted. +Most of the development happens on GitHub. It's also where [our central repository](https://github.com/yuzu-emu/yuzu) is hosted. For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY). -If you want to contribute please take a look at the [Contributor's Guide](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information). You should also contact any of the developers on Discord 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](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information).  +You can also contact any of the developers on Discord in order to know about the current state of the emulator. -If you want to contribute to the user interface translation, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations. +If you want to contribute to the user interface translation project, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations. -### Building +## Building  * __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows)  * __Linux__: [Linux Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Linux) +## Download -### Support -We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://yuzu-emu.org/donate/) for more information on how you can contribute to yuzu. Any donations received will go towards things like: +You can download the latest releases automatically via the installer on our [downloads](https://yuzu-emu.org/downloads/) page. + + +## Support + +If you enjoy the project and want to support us financially, check out our Patreon! + +<a href="https://www.patreon.com/yuzuteam"> +    <img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160"> +</a> + +Any donations received will go towards things like:  * Switch consoles to explore and reverse-engineer the hardware  * Switch games for testing, reverse-engineering, and implementing new features  * Web hosting and infrastructure setup  * Software licenses (e.g. Visual Studio, IDA Pro, etc.)  * Additional hardware (e.g. GPUs as-needed to improve rendering support, other peripherals to add support for, etc.) -We also more than gladly accept used Switch consoles, preferably ones with firmware 3.0.0 or lower! If you would like to give yours away, don't hesitate to join our [Discord](https://discord.gg/VXqngT3) and talk to bunnei. You may also contact: donations@yuzu-emu.org. +If you wish to support us a different way, please join our [Discord](https://discord.gg/u77vRWY) and talk to bunnei. You may also contact: donations@yuzu-emu.org. + +## License + +yuzu is licensed under the GPLv2 (or any later version). Refer to the [license.txt](https://github.com/yuzu-emu/yuzu/blob/master/license.txt) file. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 5402a532f..fd427a912 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -53,10 +53,10 @@ endif()  # SDL2  if (NOT SDL2_FOUND AND ENABLE_SDL2)      if (NOT WIN32) -        # Yuzu itself needs: Events Joystick Haptic Sensor Timers +        # Yuzu itself needs: Events Joystick Haptic Sensor Timers Audio          # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)          set(SDL_UNUSED_SUBSYSTEMS -            Atomic Audio Render Power Threads +            Atomic Render Power Threads              File CPUinfo Filesystem Locale)          foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})            string(TOUPPER ${_SUB} _OPT) @@ -115,7 +115,7 @@ if (ENABLE_WEB_SERVICE)      # httplib      add_library(httplib INTERFACE) -    target_include_directories(httplib INTERFACE ./httplib) +    target_include_directories(httplib INTERFACE ./cpp-httplib)      target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)      target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})      if (WIN32) diff --git a/externals/cpp-httplib b/externals/cpp-httplib new file mode 160000 +Subproject 9648f950f5a8a41d18833cf4a85f5821b1bcac5 diff --git a/externals/discord-rpc b/externals/discord-rpc -Subproject e32d001809c4aad56cef2a5321b54442d830174 +Subproject 963aa9f3e5ce81a4682c6ca3d136cddda614db3 diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 0c12614d1a7a72d778609920dde96a4c63074ec +Subproject 7946868af49d403fe54c92d2d60ef986513d1fe diff --git a/externals/httplib/README.md b/externals/httplib/README.md deleted file mode 100644 index 1940e446c..000000000 --- a/externals/httplib/README.md +++ /dev/null @@ -1,15 +0,0 @@ -From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701 - -MIT License - -=== - -cpp-httplib - -A C++11 header-only HTTP library. - -It's extremely easy to setup. Just include httplib.h file in your code! - -Inspired by Sinatra and express. - -© 2017 Yuji Hirose diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h deleted file mode 100644 index 8982054e2..000000000 --- a/externals/httplib/httplib.h +++ /dev/null @@ -1,6714 +0,0 @@ -// -//  httplib.h -// -//  Copyright (c) 2020 Yuji Hirose. All rights reserved. -//  MIT License -// - -#ifndef CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_HTTPLIB_H - -/* - * Configuration - */ - -#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 -#endif - -#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT -#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 -#endif - -#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND -#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 -#endif - -#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND -#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 -#endif - -#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND -#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 -#endif - -#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND -#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 -#endif - -#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND -#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 -#endif - -#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND -#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 -#endif - -#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND -#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 -#endif - -#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND -#ifdef _WIN32 -#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 -#else -#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 -#endif -#endif - -#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH -#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 -#endif - -#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT -#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 -#endif - -#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH -#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)()) -#endif - -#ifndef CPPHTTPLIB_TCP_NODELAY -#define CPPHTTPLIB_TCP_NODELAY false -#endif - -#ifndef CPPHTTPLIB_RECV_BUFSIZ -#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) -#endif - -#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ -#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) -#endif - -#ifndef CPPHTTPLIB_THREAD_POOL_COUNT -#define CPPHTTPLIB_THREAD_POOL_COUNT                                           \ -  ((std::max)(8u, std::thread::hardware_concurrency() > 0                      \ -                      ? std::thread::hardware_concurrency() - 1                \ -                      : 0)) -#endif - -/* - * Headers - */ - -#ifdef _WIN32 -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif //_CRT_SECURE_NO_WARNINGS - -#ifndef _CRT_NONSTDC_NO_DEPRECATE -#define _CRT_NONSTDC_NO_DEPRECATE -#endif //_CRT_NONSTDC_NO_DEPRECATE - -#if defined(_MSC_VER) -#ifdef _WIN64 -using ssize_t = __int64; -#else -using ssize_t = int; -#endif - -#if _MSC_VER < 1900 -#define snprintf _snprintf_s -#endif -#endif // _MSC_VER - -#ifndef S_ISREG -#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) -#endif // S_ISREG - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) -#endif // S_ISDIR - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -#include <io.h> -#include <winsock2.h> - -#include <wincrypt.h> -#include <ws2tcpip.h> - -#ifndef WSA_FLAG_NO_HANDLE_INHERIT -#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 -#endif - -#ifdef _MSC_VER -#pragma comment(lib, "ws2_32.lib") -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "cryptui.lib") -#endif - -#ifndef strcasecmp -#define strcasecmp _stricmp -#endif // strcasecmp - -using socket_t = SOCKET; -#ifdef CPPHTTPLIB_USE_POLL -#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) -#endif - -#else // not _WIN32 - -#include <arpa/inet.h> -#include <cstring> -#include <ifaddrs.h> -#include <netdb.h> -#include <netinet/in.h> -#ifdef __linux__ -#include <resolv.h> -#endif -#include <netinet/tcp.h> -#ifdef CPPHTTPLIB_USE_POLL -#include <poll.h> -#endif -#include <csignal> -#include <pthread.h> -#include <sys/select.h> -#include <sys/socket.h> -#include <unistd.h> - -using socket_t = int; -#define INVALID_SOCKET (-1) -#endif //_WIN32 - -#include <algorithm> -#include <array> -#include <atomic> -#include <cassert> -#include <cctype> -#include <climits> -#include <condition_variable> -#include <errno.h> -#include <fcntl.h> -#include <fstream> -#include <functional> -#include <iostream> -#include <list> -#include <map> -#include <memory> -#include <mutex> -#include <random> -#include <regex> -#include <sstream> -#include <string> -#include <sys/stat.h> -#include <thread> - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -#include <openssl/err.h> -#include <openssl/md5.h> -#include <openssl/ssl.h> -#include <openssl/x509v3.h> - -#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) -#include <openssl/applink.c> -#endif - -#include <iomanip> -#include <iostream> -#include <sstream> - -#if OPENSSL_VERSION_NUMBER < 0x1010100fL -#error Sorry, OpenSSL versions prior to 1.1.1 are not supported -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#include <openssl/crypto.h> -inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { -  return M_ASN1_STRING_data(asn1); -} -#endif -#endif - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -#include <zlib.h> -#endif - -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -#include <brotli/decode.h> -#include <brotli/encode.h> -#endif - -/* - * Declaration - */ -namespace httplib { - -namespace detail { - -/* - * Backport std::make_unique from C++14. - * - * NOTE: This code came up with the following stackoverflow post: - * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique - * - */ - -template <class T, class... Args> -typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type -make_unique(Args &&... args) { -  return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - -template <class T> -typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type -make_unique(std::size_t n) { -  typedef typename std::remove_extent<T>::type RT; -  return std::unique_ptr<T>(new RT[n]); -} - -struct ci { -  bool operator()(const std::string &s1, const std::string &s2) const { -    return std::lexicographical_compare( -        s1.begin(), s1.end(), s2.begin(), s2.end(), -        [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); -  } -}; - -} // namespace detail - -using Headers = std::multimap<std::string, std::string, detail::ci>; - -using Params = std::multimap<std::string, std::string>; -using Match = std::smatch; - -using Progress = std::function<bool(uint64_t current, uint64_t total)>; - -struct Response; -using ResponseHandler = std::function<bool(const Response &response)>; - -struct MultipartFormData { -  std::string name; -  std::string content; -  std::string filename; -  std::string content_type; -}; -using MultipartFormDataItems = std::vector<MultipartFormData>; -using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>; - -class DataSink { -public: -  DataSink() : os(&sb_), sb_(*this) {} - -  DataSink(const DataSink &) = delete; -  DataSink &operator=(const DataSink &) = delete; -  DataSink(DataSink &&) = delete; -  DataSink &operator=(DataSink &&) = delete; - -  std::function<void(const char *data, size_t data_len)> write; -  std::function<void()> done; -  std::function<bool()> is_writable; -  std::ostream os; - -private: -  class data_sink_streambuf : public std::streambuf { -  public: -    explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} - -  protected: -    std::streamsize xsputn(const char *s, std::streamsize n) { -      sink_.write(s, static_cast<size_t>(n)); -      return n; -    } - -  private: -    DataSink &sink_; -  }; - -  data_sink_streambuf sb_; -}; - -using ContentProvider = -    std::function<bool(size_t offset, size_t length, DataSink &sink)>; - -using ContentProviderWithoutLength = -    std::function<bool(size_t offset, DataSink &sink)>; - -using ContentReceiverWithProgress = -    std::function<bool(const char *data, size_t data_length, uint64_t offset, -                       uint64_t total_length)>; - -using ContentReceiver = -    std::function<bool(const char *data, size_t data_length)>; - -using MultipartContentHeader = -    std::function<bool(const MultipartFormData &file)>; - -class ContentReader { -public: -  using Reader = std::function<bool(ContentReceiver receiver)>; -  using MultipartReader = std::function<bool(MultipartContentHeader header, -                                             ContentReceiver receiver)>; - -  ContentReader(Reader reader, MultipartReader multipart_reader) -      : reader_(std::move(reader)), -        multipart_reader_(std::move(multipart_reader)) {} - -  bool operator()(MultipartContentHeader header, -                  ContentReceiver receiver) const { -    return multipart_reader_(std::move(header), std::move(receiver)); -  } - -  bool operator()(ContentReceiver receiver) const { -    return reader_(std::move(receiver)); -  } - -  Reader reader_; -  MultipartReader multipart_reader_; -}; - -using Range = std::pair<ssize_t, ssize_t>; -using Ranges = std::vector<Range>; - -struct Request { -  std::string method; -  std::string path; -  Headers headers; -  std::string body; - -  std::string remote_addr; -  int remote_port = -1; - -  // for server -  std::string version; -  std::string target; -  Params params; -  MultipartFormDataMap files; -  Ranges ranges; -  Match matches; - -  // for client -  size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT; -  ResponseHandler response_handler; -  ContentReceiverWithProgress content_receiver; -  size_t content_length = 0; -  ContentProvider content_provider; -  Progress progress; - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  const SSL *ssl; -#endif - -  bool has_header(const char *key) const; -  std::string get_header_value(const char *key, size_t id = 0) const; -  template <typename T> -  T get_header_value(const char *key, size_t id = 0) const; -  size_t get_header_value_count(const char *key) const; -  void set_header(const char *key, const char *val); -  void set_header(const char *key, const std::string &val); - -  bool has_param(const char *key) const; -  std::string get_param_value(const char *key, size_t id = 0) const; -  size_t get_param_value_count(const char *key) const; - -  bool is_multipart_form_data() const; - -  bool has_file(const char *key) const; -  MultipartFormData get_file_value(const char *key) const; - -  // private members... -  size_t authorization_count_ = 0; -}; - -struct Response { -  std::string version; -  int status = -1; -  std::string reason; -  Headers headers; -  std::string body; - -  bool has_header(const char *key) const; -  std::string get_header_value(const char *key, size_t id = 0) const; -  template <typename T> -  T get_header_value(const char *key, size_t id = 0) const; -  size_t get_header_value_count(const char *key) const; -  void set_header(const char *key, const char *val); -  void set_header(const char *key, const std::string &val); - -  void set_redirect(const char *url, int status = 302); -  void set_redirect(const std::string &url, int status = 302); -  void set_content(const char *s, size_t n, const char *content_type); -  void set_content(std::string s, const char *content_type); - -  void set_content_provider( -      size_t length, const char *content_type, ContentProvider provider, -      const std::function<void()> &resource_releaser = nullptr); - -  void set_content_provider( -      const char *content_type, ContentProviderWithoutLength provider, -      const std::function<void()> &resource_releaser = nullptr); - -  void set_chunked_content_provider( -      const char *content_type, ContentProviderWithoutLength provider, -      const std::function<void()> &resource_releaser = nullptr); - -  Response() = default; -  Response(const Response &) = default; -  Response &operator=(const Response &) = default; -  Response(Response &&) = default; -  Response &operator=(Response &&) = default; -  ~Response() { -    if (content_provider_resource_releaser_) { -      content_provider_resource_releaser_(); -    } -  } - -  // private members... -  size_t content_length_ = 0; -  ContentProvider content_provider_; -  std::function<void()> content_provider_resource_releaser_; -  bool is_chunked_content_provider = false; -}; - -class Stream { -public: -  virtual ~Stream() = default; - -  virtual bool is_readable() const = 0; -  virtual bool is_writable() const = 0; - -  virtual ssize_t read(char *ptr, size_t size) = 0; -  virtual ssize_t write(const char *ptr, size_t size) = 0; -  virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; - -  template <typename... Args> -  ssize_t write_format(const char *fmt, const Args &... args); -  ssize_t write(const char *ptr); -  ssize_t write(const std::string &s); -}; - -class TaskQueue { -public: -  TaskQueue() = default; -  virtual ~TaskQueue() = default; - -  virtual void enqueue(std::function<void()> fn) = 0; -  virtual void shutdown() = 0; - -  virtual void on_idle(){}; -}; - -class ThreadPool : public TaskQueue { -public: -  explicit ThreadPool(size_t n) : shutdown_(false) { -    while (n) { -      threads_.emplace_back(worker(*this)); -      n--; -    } -  } - -  ThreadPool(const ThreadPool &) = delete; -  ~ThreadPool() override = default; - -  void enqueue(std::function<void()> fn) override { -    std::unique_lock<std::mutex> lock(mutex_); -    jobs_.push_back(std::move(fn)); -    cond_.notify_one(); -  } - -  void shutdown() override { -    // Stop all worker threads... -    { -      std::unique_lock<std::mutex> lock(mutex_); -      shutdown_ = true; -    } - -    cond_.notify_all(); - -    // Join... -    for (auto &t : threads_) { -      t.join(); -    } -  } - -private: -  struct worker { -    explicit worker(ThreadPool &pool) : pool_(pool) {} - -    void operator()() { -      for (;;) { -        std::function<void()> fn; -        { -          std::unique_lock<std::mutex> lock(pool_.mutex_); - -          pool_.cond_.wait( -              lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); - -          if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } - -          fn = pool_.jobs_.front(); -          pool_.jobs_.pop_front(); -        } - -        assert(true == static_cast<bool>(fn)); -        fn(); -      } -    } - -    ThreadPool &pool_; -  }; -  friend struct worker; - -  std::vector<std::thread> threads_; -  std::list<std::function<void()>> jobs_; - -  bool shutdown_; - -  std::condition_variable cond_; -  std::mutex mutex_; -}; - -using Logger = std::function<void(const Request &, const Response &)>; - -using SocketOptions = std::function<void(socket_t sock)>; - -inline void default_socket_options(socket_t sock) { -  int yes = 1; -#ifdef _WIN32 -  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes), -             sizeof(yes)); -  setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, -             reinterpret_cast<char *>(&yes), sizeof(yes)); -#else -#ifdef SO_REUSEPORT -  setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes), -             sizeof(yes)); -#else -  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes), -             sizeof(yes)); -#endif -#endif -} - -class Server { -public: -  using Handler = std::function<void(const Request &, Response &)>; -  using HandlerWithContentReader = std::function<void( -      const Request &, Response &, const ContentReader &content_reader)>; -  using Expect100ContinueHandler = -      std::function<int(const Request &, Response &)>; - -  Server(); - -  virtual ~Server(); - -  virtual bool is_valid() const; - -  Server &Get(const char *pattern, Handler handler); -  Server &Post(const char *pattern, Handler handler); -  Server &Post(const char *pattern, HandlerWithContentReader handler); -  Server &Put(const char *pattern, Handler handler); -  Server &Put(const char *pattern, HandlerWithContentReader handler); -  Server &Patch(const char *pattern, Handler handler); -  Server &Patch(const char *pattern, HandlerWithContentReader handler); -  Server &Delete(const char *pattern, Handler handler); -  Server &Delete(const char *pattern, HandlerWithContentReader handler); -  Server &Options(const char *pattern, Handler handler); - -  bool set_base_dir(const char *dir, const char *mount_point = nullptr); -  bool set_mount_point(const char *mount_point, const char *dir, -                       Headers headers = Headers()); -  bool remove_mount_point(const char *mount_point); -  void set_file_extension_and_mimetype_mapping(const char *ext, -                                               const char *mime); -  void set_file_request_handler(Handler handler); - -  void set_error_handler(Handler handler); -  void set_expect_100_continue_handler(Expect100ContinueHandler handler); -  void set_logger(Logger logger); - -  void set_tcp_nodelay(bool on); -  void set_socket_options(SocketOptions socket_options); - -  void set_keep_alive_max_count(size_t count); -  void set_keep_alive_timeout(time_t sec); -  void set_read_timeout(time_t sec, time_t usec = 0); -  void set_write_timeout(time_t sec, time_t usec = 0); -  void set_idle_interval(time_t sec, time_t usec = 0); - -  void set_payload_max_length(size_t length); - -  bool bind_to_port(const char *host, int port, int socket_flags = 0); -  int bind_to_any_port(const char *host, int socket_flags = 0); -  bool listen_after_bind(); - -  bool listen(const char *host, int port, int socket_flags = 0); - -  bool is_running() const; -  void stop(); - -  std::function<TaskQueue *(void)> new_task_queue; - -protected: -  bool process_request(Stream &strm, bool close_connection, -                       bool &connection_closed, -                       const std::function<void(Request &)> &setup_request); - -  std::atomic<socket_t> svr_sock_; -  size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; -  time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; -  time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; -  time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; -  time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; -  time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; -  time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; -  time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; -  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; - -private: -  using Handlers = std::vector<std::pair<std::regex, Handler>>; -  using HandlersForContentReader = -      std::vector<std::pair<std::regex, HandlerWithContentReader>>; - -  socket_t create_server_socket(const char *host, int port, int socket_flags, -                                SocketOptions socket_options) const; -  int bind_internal(const char *host, int port, int socket_flags); -  bool listen_internal(); - -  bool routing(Request &req, Response &res, Stream &strm); -  bool handle_file_request(Request &req, Response &res, bool head = false); -  bool dispatch_request(Request &req, Response &res, const Handlers &handlers); -  bool -  dispatch_request_for_content_reader(Request &req, Response &res, -                                      ContentReader content_reader, -                                      const HandlersForContentReader &handlers); - -  bool parse_request_line(const char *s, Request &req); -  bool write_response(Stream &strm, bool close_connection, const Request &req, -                      Response &res); -  bool write_content_with_provider(Stream &strm, const Request &req, -                                   Response &res, const std::string &boundary, -                                   const std::string &content_type); -  bool read_content(Stream &strm, Request &req, Response &res); -  bool -  read_content_with_content_receiver(Stream &strm, Request &req, Response &res, -                                     ContentReceiver receiver, -                                     MultipartContentHeader multipart_header, -                                     ContentReceiver multipart_receiver); -  bool read_content_core(Stream &strm, Request &req, Response &res, -                         ContentReceiver receiver, -                         MultipartContentHeader mulitpart_header, -                         ContentReceiver multipart_receiver); - -  virtual bool process_and_close_socket(socket_t sock); - -  struct MountPointEntry { -    std::string mount_point; -    std::string base_dir; -    Headers headers; -  }; -  std::vector<MountPointEntry> base_dirs_; - -  std::atomic<bool> is_running_; -  std::map<std::string, std::string> file_extension_and_mimetype_map_; -  Handler file_request_handler_; -  Handlers get_handlers_; -  Handlers post_handlers_; -  HandlersForContentReader post_handlers_for_content_reader_; -  Handlers put_handlers_; -  HandlersForContentReader put_handlers_for_content_reader_; -  Handlers patch_handlers_; -  HandlersForContentReader patch_handlers_for_content_reader_; -  Handlers delete_handlers_; -  HandlersForContentReader delete_handlers_for_content_reader_; -  Handlers options_handlers_; -  Handler error_handler_; -  Logger logger_; -  Expect100ContinueHandler expect_100_continue_handler_; - -  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; -  SocketOptions socket_options_ = default_socket_options; -}; - -enum Error { -  Success = 0, -  Unknown, -  Connection, -  BindIPAddress, -  Read, -  Write, -  ExceedRedirectCount, -  Canceled, -  SSLConnection, -  SSLLoadingCerts, -  SSLServerVerification, -  UnsupportedMultipartBoundaryChars -}; - -class Result { -public: -  Result(std::unique_ptr<Response> res, Error err) -      : res_(std::move(res)), err_(err) {} -  operator bool() const { return res_ != nullptr; } -  bool operator==(std::nullptr_t) const { return res_ == nullptr; } -  bool operator!=(std::nullptr_t) const { return res_ != nullptr; } -  const Response &value() const { return *res_; } -  Response &value() { return *res_; } -  const Response &operator*() const { return *res_; } -  Response &operator*() { return *res_; } -  const Response *operator->() const { return res_.get(); } -  Response *operator->() { return res_.get(); } -  Error error() const { return err_; } - -private: -  std::unique_ptr<Response> res_; -  Error err_; -}; - -class ClientImpl { -public: -  explicit ClientImpl(const std::string &host); - -  explicit ClientImpl(const std::string &host, int port); - -  explicit ClientImpl(const std::string &host, int port, -                      const std::string &client_cert_path, -                      const std::string &client_key_path); - -  virtual ~ClientImpl(); - -  virtual bool is_valid() const; - -  Result Get(const char *path); -  Result Get(const char *path, const Headers &headers); -  Result Get(const char *path, Progress progress); -  Result Get(const char *path, const Headers &headers, Progress progress); -  Result Get(const char *path, ContentReceiver content_receiver); -  Result Get(const char *path, const Headers &headers, -             ContentReceiver content_receiver); -  Result Get(const char *path, ContentReceiver content_receiver, -             Progress progress); -  Result Get(const char *path, const Headers &headers, -             ContentReceiver content_receiver, Progress progress); -  Result Get(const char *path, ResponseHandler response_handler, -             ContentReceiver content_receiver); -  Result Get(const char *path, const Headers &headers, -             ResponseHandler response_handler, -             ContentReceiver content_receiver); -  Result Get(const char *path, ResponseHandler response_handler, -             ContentReceiver content_receiver, Progress progress); -  Result Get(const char *path, const Headers &headers, -             ResponseHandler response_handler, ContentReceiver content_receiver, -             Progress progress); - -  Result Head(const char *path); -  Result Head(const char *path, const Headers &headers); - -  Result Post(const char *path); -  Result Post(const char *path, const std::string &body, -              const char *content_type); -  Result Post(const char *path, const Headers &headers, const std::string &body, -              const char *content_type); -  Result Post(const char *path, size_t content_length, -              ContentProvider content_provider, const char *content_type); -  Result Post(const char *path, const Headers &headers, size_t content_length, -              ContentProvider content_provider, const char *content_type); -  Result Post(const char *path, const Params ¶ms); -  Result Post(const char *path, const Headers &headers, const Params ¶ms); -  Result Post(const char *path, const MultipartFormDataItems &items); -  Result Post(const char *path, const Headers &headers, -              const MultipartFormDataItems &items); -  Result Post(const char *path, const Headers &headers, -              const MultipartFormDataItems &items, const std::string &boundary); - -  Result Put(const char *path); -  Result Put(const char *path, const std::string &body, -             const char *content_type); -  Result Put(const char *path, const Headers &headers, const std::string &body, -             const char *content_type); -  Result Put(const char *path, size_t content_length, -             ContentProvider content_provider, const char *content_type); -  Result Put(const char *path, const Headers &headers, size_t content_length, -             ContentProvider content_provider, const char *content_type); -  Result Put(const char *path, const Params ¶ms); -  Result Put(const char *path, const Headers &headers, const Params ¶ms); - -  Result Patch(const char *path, const std::string &body, -               const char *content_type); -  Result Patch(const char *path, const Headers &headers, -               const std::string &body, const char *content_type); -  Result Patch(const char *path, size_t content_length, -               ContentProvider content_provider, const char *content_type); -  Result Patch(const char *path, const Headers &headers, size_t content_length, -               ContentProvider content_provider, const char *content_type); - -  Result Delete(const char *path); -  Result Delete(const char *path, const std::string &body, -                const char *content_type); -  Result Delete(const char *path, const Headers &headers); -  Result Delete(const char *path, const Headers &headers, -                const std::string &body, const char *content_type); - -  Result Options(const char *path); -  Result Options(const char *path, const Headers &headers); - -  bool send(const Request &req, Response &res); - -  size_t is_socket_open() const; - -  void stop(); - -  void set_default_headers(Headers headers); - -  void set_tcp_nodelay(bool on); -  void set_socket_options(SocketOptions socket_options); - -  void set_connection_timeout(time_t sec, time_t usec = 0); -  void set_read_timeout(time_t sec, time_t usec = 0); -  void set_write_timeout(time_t sec, time_t usec = 0); - -  void set_basic_auth(const char *username, const char *password); -  void set_bearer_token_auth(const char *token); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void set_digest_auth(const char *username, const char *password); -#endif - -  void set_keep_alive(bool on); -  void set_follow_location(bool on); - -  void set_compress(bool on); - -  void set_decompress(bool on); - -  void set_interface(const char *intf); - -  void set_proxy(const char *host, int port); -  void set_proxy_basic_auth(const char *username, const char *password); -  void set_proxy_bearer_token_auth(const char *token); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void set_proxy_digest_auth(const char *username, const char *password); -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void enable_server_certificate_verification(bool enabled); -#endif - -  void set_logger(Logger logger); - -protected: -  struct Socket { -    socket_t sock = INVALID_SOCKET; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -    SSL *ssl = nullptr; -#endif - -    bool is_open() const { return sock != INVALID_SOCKET; } -  }; - -  virtual bool create_and_connect_socket(Socket &socket); - -  // All of: -  //   shutdown_ssl -  //   shutdown_socket -  //   close_socket -  // should ONLY be called when socket_mutex_ is locked. -  // Also, shutdown_ssl and close_socket should also NOT be called concurrently -  // with a DIFFERENT thread sending requests using that socket. -  virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); -  void shutdown_socket(Socket &socket); -  void close_socket(Socket &socket); - -  // Similar to shutdown_ssl and close_socket, this should NOT be called -  // concurrently with a DIFFERENT thread sending requests from the socket -  void lock_socket_and_shutdown_and_close(); - -  bool process_request(Stream &strm, const Request &req, Response &res, -                       bool close_connection); - -  Error get_last_error() const; - -  void copy_settings(const ClientImpl &rhs); - -  // Error state -  mutable std::atomic<Error> error_; - -  // Socket endoint information -  const std::string host_; -  const int port_; -  const std::string host_and_port_; - -  // Current open socket -  Socket socket_; -  mutable std::mutex socket_mutex_; -  std::recursive_mutex request_mutex_; - -  // These are all protected under socket_mutex -  int socket_requests_in_flight_ = 0; -  std::thread::id socket_requests_are_from_thread_ = std::thread::id(); -  bool socket_should_be_closed_when_request_is_done_ = false; - -  // Default headers -  Headers default_headers_; - -  // Settings -  std::string client_cert_path_; -  std::string client_key_path_; - -  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; -  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; -  time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; -  time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; -  time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; -  time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; - -  std::string basic_auth_username_; -  std::string basic_auth_password_; -  std::string bearer_token_auth_token_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  std::string digest_auth_username_; -  std::string digest_auth_password_; -#endif - -  bool keep_alive_ = false; -  bool follow_location_ = false; - -  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; -  SocketOptions socket_options_ = nullptr; - -  bool compress_ = false; -  bool decompress_ = true; - -  std::string interface_; - -  std::string proxy_host_; -  int proxy_port_ = -1; - -  std::string proxy_basic_auth_username_; -  std::string proxy_basic_auth_password_; -  std::string proxy_bearer_token_auth_token_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  std::string proxy_digest_auth_username_; -  std::string proxy_digest_auth_password_; -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  bool server_certificate_verification_ = true; -#endif - -  Logger logger_; - -private: -  socket_t create_client_socket() const; -  bool read_response_line(Stream &strm, Response &res); -  bool write_request(Stream &strm, const Request &req, bool close_connection); -  bool redirect(const Request &req, Response &res); -  bool handle_request(Stream &strm, const Request &req, Response &res, -                      bool close_connection); -  std::unique_ptr<Response> send_with_content_provider( -      const char *method, const char *path, const Headers &headers, -      const std::string &body, size_t content_length, -      ContentProvider content_provider, const char *content_type); - -  // socket is const because this function is called when socket_mutex_ is not locked -  virtual bool process_socket(const Socket &socket, -                              std::function<bool(Stream &strm)> callback); -  virtual bool is_ssl() const; -}; - -class Client { -public: -  // Universal interface -  explicit Client(const char *scheme_host_port); - -  explicit Client(const char *scheme_host_port, -                  const std::string &client_cert_path, -                  const std::string &client_key_path); - -  // HTTP only interface -  explicit Client(const std::string &host, int port); - -  explicit Client(const std::string &host, int port, -                  const std::string &client_cert_path, -                  const std::string &client_key_path); - -  ~Client(); - -  bool is_valid() const; - -  Result Get(const char *path); -  Result Get(const char *path, const Headers &headers); -  Result Get(const char *path, Progress progress); -  Result Get(const char *path, const Headers &headers, Progress progress); -  Result Get(const char *path, ContentReceiver content_receiver); -  Result Get(const char *path, const Headers &headers, -             ContentReceiver content_receiver); -  Result Get(const char *path, ContentReceiver content_receiver, -             Progress progress); -  Result Get(const char *path, const Headers &headers, -             ContentReceiver content_receiver, Progress progress); -  Result Get(const char *path, ResponseHandler response_handler, -             ContentReceiver content_receiver); -  Result Get(const char *path, const Headers &headers, -             ResponseHandler response_handler, -             ContentReceiver content_receiver); -  Result Get(const char *path, const Headers &headers, -             ResponseHandler response_handler, ContentReceiver content_receiver, -             Progress progress); -  Result Get(const char *path, ResponseHandler response_handler, -             ContentReceiver content_receiver, Progress progress); - -  Result Head(const char *path); -  Result Head(const char *path, const Headers &headers); - -  Result Post(const char *path); -  Result Post(const char *path, const std::string &body, -              const char *content_type); -  Result Post(const char *path, const Headers &headers, const std::string &body, -              const char *content_type); -  Result Post(const char *path, size_t content_length, -              ContentProvider content_provider, const char *content_type); -  Result Post(const char *path, const Headers &headers, size_t content_length, -              ContentProvider content_provider, const char *content_type); -  Result Post(const char *path, const Params ¶ms); -  Result Post(const char *path, const Headers &headers, const Params ¶ms); -  Result Post(const char *path, const MultipartFormDataItems &items); -  Result Post(const char *path, const Headers &headers, -              const MultipartFormDataItems &items); -  Result Post(const char *path, const Headers &headers, -              const MultipartFormDataItems &items, const std::string &boundary); -  Result Put(const char *path); -  Result Put(const char *path, const std::string &body, -             const char *content_type); -  Result Put(const char *path, const Headers &headers, const std::string &body, -             const char *content_type); -  Result Put(const char *path, size_t content_length, -             ContentProvider content_provider, const char *content_type); -  Result Put(const char *path, const Headers &headers, size_t content_length, -             ContentProvider content_provider, const char *content_type); -  Result Put(const char *path, const Params ¶ms); -  Result Put(const char *path, const Headers &headers, const Params ¶ms); -  Result Patch(const char *path, const std::string &body, -               const char *content_type); -  Result Patch(const char *path, const Headers &headers, -               const std::string &body, const char *content_type); -  Result Patch(const char *path, size_t content_length, -               ContentProvider content_provider, const char *content_type); -  Result Patch(const char *path, const Headers &headers, size_t content_length, -               ContentProvider content_provider, const char *content_type); - -  Result Delete(const char *path); -  Result Delete(const char *path, const std::string &body, -                const char *content_type); -  Result Delete(const char *path, const Headers &headers); -  Result Delete(const char *path, const Headers &headers, -                const std::string &body, const char *content_type); - -  Result Options(const char *path); -  Result Options(const char *path, const Headers &headers); - -  bool send(const Request &req, Response &res); - -  size_t is_socket_open() const; - -  void stop(); - -  void set_default_headers(Headers headers); - -  void set_tcp_nodelay(bool on); -  void set_socket_options(SocketOptions socket_options); - -  void set_connection_timeout(time_t sec, time_t usec = 0); -  void set_read_timeout(time_t sec, time_t usec = 0); -  void set_write_timeout(time_t sec, time_t usec = 0); - -  void set_basic_auth(const char *username, const char *password); -  void set_bearer_token_auth(const char *token); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void set_digest_auth(const char *username, const char *password); -#endif - -  void set_keep_alive(bool on); -  void set_follow_location(bool on); - -  void set_compress(bool on); - -  void set_decompress(bool on); - -  void set_interface(const char *intf); - -  void set_proxy(const char *host, int port); -  void set_proxy_basic_auth(const char *username, const char *password); -  void set_proxy_bearer_token_auth(const char *token); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void set_proxy_digest_auth(const char *username, const char *password); -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void enable_server_certificate_verification(bool enabled); -#endif - -  void set_logger(Logger logger); - -  // SSL -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  void set_ca_cert_path(const char *ca_cert_file_path, -                        const char *ca_cert_dir_path = nullptr); - -  void set_ca_cert_store(X509_STORE *ca_cert_store); - -  long get_openssl_verify_result() const; - -  SSL_CTX *ssl_context() const; -#endif - -private: -  std::unique_ptr<ClientImpl> cli_; - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  bool is_ssl_ = false; -#endif -}; // namespace httplib - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -class SSLServer : public Server { -public: -  SSLServer(const char *cert_path, const char *private_key_path, -            const char *client_ca_cert_file_path = nullptr, -            const char *client_ca_cert_dir_path = nullptr); - -  SSLServer(X509 *cert, EVP_PKEY *private_key, -            X509_STORE *client_ca_cert_store = nullptr); - -  ~SSLServer() override; - -  bool is_valid() const override; - -private: -  bool process_and_close_socket(socket_t sock) override; - -  SSL_CTX *ctx_; -  std::mutex ctx_mutex_; -}; - -class SSLClient : public ClientImpl { -public: -  explicit SSLClient(const std::string &host); - -  explicit SSLClient(const std::string &host, int port); - -  explicit SSLClient(const std::string &host, int port, -                     const std::string &client_cert_path, -                     const std::string &client_key_path); - -  explicit SSLClient(const std::string &host, int port, X509 *client_cert, -                     EVP_PKEY *client_key); - -  ~SSLClient() override; - -  bool is_valid() const override; - -  void set_ca_cert_path(const char *ca_cert_file_path, -                        const char *ca_cert_dir_path = nullptr); - -  void set_ca_cert_store(X509_STORE *ca_cert_store); - -  long get_openssl_verify_result() const; - -  SSL_CTX *ssl_context() const; - -private: -  bool create_and_connect_socket(Socket &socket) override; -  void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; - -  bool process_socket(const Socket &socket, -                      std::function<bool(Stream &strm)> callback) override; -  bool is_ssl() const override; - -  bool connect_with_proxy(Socket &sock, Response &res, bool &success); -  bool initialize_ssl(Socket &socket); - -  bool load_certs(); - -  bool verify_host(X509 *server_cert) const; -  bool verify_host_with_subject_alt_name(X509 *server_cert) const; -  bool verify_host_with_common_name(X509 *server_cert) const; -  bool check_host_name(const char *pattern, size_t pattern_len) const; - -  SSL_CTX *ctx_; -  std::mutex ctx_mutex_; -  std::once_flag initialize_cert_; - -  std::vector<std::string> host_components_; - -  std::string ca_cert_file_path_; -  std::string ca_cert_dir_path_; -  long verify_result_ = 0; - -  friend class ClientImpl; -}; -#endif - -// ---------------------------------------------------------------------------- - -/* - * Implementation - */ - -namespace detail { - -inline bool is_hex(char c, int &v) { -  if (0x20 <= c && isdigit(c)) { -    v = c - '0'; -    return true; -  } else if ('A' <= c && c <= 'F') { -    v = c - 'A' + 10; -    return true; -  } else if ('a' <= c && c <= 'f') { -    v = c - 'a' + 10; -    return true; -  } -  return false; -} - -inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, -                          int &val) { -  if (i >= s.size()) { return false; } - -  val = 0; -  for (; cnt; i++, cnt--) { -    if (!s[i]) { return false; } -    int v = 0; -    if (is_hex(s[i], v)) { -      val = val * 16 + v; -    } else { -      return false; -    } -  } -  return true; -} - -inline std::string from_i_to_hex(size_t n) { -  const char *charset = "0123456789abcdef"; -  std::string ret; -  do { -    ret = charset[n & 15] + ret; -    n >>= 4; -  } while (n > 0); -  return ret; -} - -inline bool start_with(const std::string &a, const std::string &b) { -  if (a.size() < b.size()) { return false; } -  for (size_t i = 0; i < b.size(); i++) { -    if (::tolower(a[i]) != ::tolower(b[i])) { return false; } -  } -  return true; -} - -inline size_t to_utf8(int code, char *buff) { -  if (code < 0x0080) { -    buff[0] = (code & 0x7F); -    return 1; -  } else if (code < 0x0800) { -    buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F)); -    buff[1] = static_cast<char>(0x80 | (code & 0x3F)); -    return 2; -  } else if (code < 0xD800) { -    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF)); -    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); -    buff[2] = static_cast<char>(0x80 | (code & 0x3F)); -    return 3; -  } else if (code < 0xE000) { // D800 - DFFF is invalid... -    return 0; -  } else if (code < 0x10000) { -    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF)); -    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); -    buff[2] = static_cast<char>(0x80 | (code & 0x3F)); -    return 3; -  } else if (code < 0x110000) { -    buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7)); -    buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F)); -    buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); -    buff[3] = static_cast<char>(0x80 | (code & 0x3F)); -    return 4; -  } - -  // NOTREACHED -  return 0; -} - -// NOTE: This code came up with the following stackoverflow post: -// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c -inline std::string base64_encode(const std::string &in) { -  static const auto lookup = -      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -  std::string out; -  out.reserve(in.size()); - -  int val = 0; -  int valb = -6; - -  for (auto c : in) { -    val = (val << 8) + static_cast<uint8_t>(c); -    valb += 8; -    while (valb >= 0) { -      out.push_back(lookup[(val >> valb) & 0x3F]); -      valb -= 6; -    } -  } - -  if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } - -  while (out.size() % 4) { -    out.push_back('='); -  } - -  return out; -} - -inline bool is_file(const std::string &path) { -  struct stat st; -  return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); -} - -inline bool is_dir(const std::string &path) { -  struct stat st; -  return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); -} - -inline bool is_valid_path(const std::string &path) { -  size_t level = 0; -  size_t i = 0; - -  // Skip slash -  while (i < path.size() && path[i] == '/') { -    i++; -  } - -  while (i < path.size()) { -    // Read component -    auto beg = i; -    while (i < path.size() && path[i] != '/') { -      i++; -    } - -    auto len = i - beg; -    assert(len > 0); - -    if (!path.compare(beg, len, ".")) { -      ; -    } else if (!path.compare(beg, len, "..")) { -      if (level == 0) { return false; } -      level--; -    } else { -      level++; -    } - -    // Skip slash -    while (i < path.size() && path[i] == '/') { -      i++; -    } -  } - -  return true; -} - -inline std::string encode_url(const std::string &s) { -  std::string result; - -  for (size_t i = 0; s[i]; i++) { -    switch (s[i]) { -    case ' ': result += "%20"; break; -    case '+': result += "%2B"; break; -    case '\r': result += "%0D"; break; -    case '\n': result += "%0A"; break; -    case '\'': result += "%27"; break; -    case ',': result += "%2C"; break; -    // case ':': result += "%3A"; break; // ok? probably... -    case ';': result += "%3B"; break; -    default: -      auto c = static_cast<uint8_t>(s[i]); -      if (c >= 0x80) { -        result += '%'; -        char hex[4]; -        auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); -        assert(len == 2); -        result.append(hex, static_cast<size_t>(len)); -      } else { -        result += s[i]; -      } -      break; -    } -  } - -  return result; -} - -inline std::string decode_url(const std::string &s, -                              bool convert_plus_to_space) { -  std::string result; - -  for (size_t i = 0; i < s.size(); i++) { -    if (s[i] == '%' && i + 1 < s.size()) { -      if (s[i + 1] == 'u') { -        int val = 0; -        if (from_hex_to_i(s, i + 2, 4, val)) { -          // 4 digits Unicode codes -          char buff[4]; -          size_t len = to_utf8(val, buff); -          if (len > 0) { result.append(buff, len); } -          i += 5; // 'u0000' -        } else { -          result += s[i]; -        } -      } else { -        int val = 0; -        if (from_hex_to_i(s, i + 1, 2, val)) { -          // 2 digits hex codes -          result += static_cast<char>(val); -          i += 2; // '00' -        } else { -          result += s[i]; -        } -      } -    } else if (convert_plus_to_space && s[i] == '+') { -      result += ' '; -    } else { -      result += s[i]; -    } -  } - -  return result; -} - -inline void read_file(const std::string &path, std::string &out) { -  std::ifstream fs(path, std::ios_base::binary); -  fs.seekg(0, std::ios_base::end); -  auto size = fs.tellg(); -  fs.seekg(0); -  out.resize(static_cast<size_t>(size)); -  fs.read(&out[0], static_cast<std::streamsize>(size)); -} - -inline std::string file_extension(const std::string &path) { -  std::smatch m; -  static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); -  if (std::regex_search(path, m, re)) { return m[1].str(); } -  return std::string(); -} - -inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } - -inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left, -                                      size_t right) { -  while (b + left < e && is_space_or_tab(b[left])) { -    left++; -  } -  while (right > 0 && is_space_or_tab(b[right - 1])) { -    right--; -  } -  return std::make_pair(left, right); -} - -inline std::string trim_copy(const std::string &s) { -  auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); -  return s.substr(r.first, r.second - r.first); -} - -template <class Fn> void split(const char *b, const char *e, char d, Fn fn) { -  size_t i = 0; -  size_t beg = 0; - -  while (e ? (b + i < e) : (b[i] != '\0')) { -    if (b[i] == d) { -      auto r = trim(b, e, beg, i); -      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } -      beg = i + 1; -    } -    i++; -  } - -  if (i) { -    auto r = trim(b, e, beg, i); -    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } -  } -} - -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. -class stream_line_reader { -public: -  stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) -      : strm_(strm), fixed_buffer_(fixed_buffer), -        fixed_buffer_size_(fixed_buffer_size) {} - -  const char *ptr() const { -    if (glowable_buffer_.empty()) { -      return fixed_buffer_; -    } else { -      return glowable_buffer_.data(); -    } -  } - -  size_t size() const { -    if (glowable_buffer_.empty()) { -      return fixed_buffer_used_size_; -    } else { -      return glowable_buffer_.size(); -    } -  } - -  bool end_with_crlf() const { -    auto end = ptr() + size(); -    return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; -  } - -  bool getline() { -    fixed_buffer_used_size_ = 0; -    glowable_buffer_.clear(); - -    for (size_t i = 0;; i++) { -      char byte; -      auto n = strm_.read(&byte, 1); - -      if (n < 0) { -        return false; -      } else if (n == 0) { -        if (i == 0) { -          return false; -        } else { -          break; -        } -      } - -      append(byte); - -      if (byte == '\n') { break; } -    } - -    return true; -  } - -private: -  void append(char c) { -    if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { -      fixed_buffer_[fixed_buffer_used_size_++] = c; -      fixed_buffer_[fixed_buffer_used_size_] = '\0'; -    } else { -      if (glowable_buffer_.empty()) { -        assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); -        glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); -      } -      glowable_buffer_ += c; -    } -  } - -  Stream &strm_; -  char *fixed_buffer_; -  const size_t fixed_buffer_size_; -  size_t fixed_buffer_used_size_ = 0; -  std::string glowable_buffer_; -}; - -inline int close_socket(socket_t sock) { -#ifdef _WIN32 -  return closesocket(sock); -#else -  return close(sock); -#endif -} - -template <typename T> inline ssize_t handle_EINTR(T fn) { -  ssize_t res = false; -  while (true) { -    res = fn(); -    if (res < 0 && errno == EINTR) { continue; } -    break; -  } -  return res; -} - -inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL -  struct pollfd pfd_read; -  pfd_read.fd = sock; -  pfd_read.events = POLLIN; - -  auto timeout = static_cast<int>(sec * 1000 + usec / 1000); - -  return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); -#else -  fd_set fds; -  FD_ZERO(&fds); -  FD_SET(sock, &fds); - -  timeval tv; -  tv.tv_sec = static_cast<long>(sec); -  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec); - -  return handle_EINTR([&]() { -    return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv); -  }); -#endif -} - -inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL -  struct pollfd pfd_read; -  pfd_read.fd = sock; -  pfd_read.events = POLLOUT; - -  auto timeout = static_cast<int>(sec * 1000 + usec / 1000); - -  return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); -#else -  fd_set fds; -  FD_ZERO(&fds); -  FD_SET(sock, &fds); - -  timeval tv; -  tv.tv_sec = static_cast<long>(sec); -  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec); - -  return handle_EINTR([&]() { -    return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv); -  }); -#endif -} - -inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL -  struct pollfd pfd_read; -  pfd_read.fd = sock; -  pfd_read.events = POLLIN | POLLOUT; - -  auto timeout = static_cast<int>(sec * 1000 + usec / 1000); - -  auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); - -  if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { -    int error = 0; -    socklen_t len = sizeof(error); -    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, -                          reinterpret_cast<char *>(&error), &len); -    return res >= 0 && !error; -  } -  return false; -#else -  fd_set fdsr; -  FD_ZERO(&fdsr); -  FD_SET(sock, &fdsr); - -  auto fdsw = fdsr; -  auto fdse = fdsr; - -  timeval tv; -  tv.tv_sec = static_cast<long>(sec); -  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec); - -  auto ret = handle_EINTR([&]() { -    return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv); -  }); - -  if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { -    int error = 0; -    socklen_t len = sizeof(error); -    return getsockopt(sock, SOL_SOCKET, SO_ERROR, -                      reinterpret_cast<char *>(&error), &len) >= 0 && -           !error; -  } -  return false; -#endif -} - -class SocketStream : public Stream { -public: -  SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, -               time_t write_timeout_sec, time_t write_timeout_usec); -  ~SocketStream() override; - -  bool is_readable() const override; -  bool is_writable() const override; -  ssize_t read(char *ptr, size_t size) override; -  ssize_t write(const char *ptr, size_t size) override; -  void get_remote_ip_and_port(std::string &ip, int &port) const override; - -private: -  socket_t sock_; -  time_t read_timeout_sec_; -  time_t read_timeout_usec_; -  time_t write_timeout_sec_; -  time_t write_timeout_usec_; -}; - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -class SSLSocketStream : public Stream { -public: -  SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, -                  time_t read_timeout_usec, time_t write_timeout_sec, -                  time_t write_timeout_usec); -  ~SSLSocketStream() override; - -  bool is_readable() const override; -  bool is_writable() const override; -  ssize_t read(char *ptr, size_t size) override; -  ssize_t write(const char *ptr, size_t size) override; -  void get_remote_ip_and_port(std::string &ip, int &port) const override; - -private: -  socket_t sock_; -  SSL *ssl_; -  time_t read_timeout_sec_; -  time_t read_timeout_usec_; -  time_t write_timeout_sec_; -  time_t write_timeout_usec_; -}; -#endif - -class BufferStream : public Stream { -public: -  BufferStream() = default; -  ~BufferStream() override = default; - -  bool is_readable() const override; -  bool is_writable() const override; -  ssize_t read(char *ptr, size_t size) override; -  ssize_t write(const char *ptr, size_t size) override; -  void get_remote_ip_and_port(std::string &ip, int &port) const override; - -  const std::string &get_buffer() const; - -private: -  std::string buffer; -  size_t position = 0; -}; - -inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { -  using namespace std::chrono; -  auto start = steady_clock::now(); -  while (true) { -    auto val = select_read(sock, 0, 10000); -    if (val < 0) { -      return false; -    } else if (val == 0) { -      auto current = steady_clock::now(); -      auto duration = duration_cast<milliseconds>(current - start); -      auto timeout = keep_alive_timeout_sec * 1000; -      if (duration.count() > timeout) { return false; } -      std::this_thread::sleep_for(std::chrono::milliseconds(1)); -    } else { -      return true; -    } -  } -} - -template <typename T> -inline bool -process_server_socket_core(socket_t sock, size_t keep_alive_max_count, -                           time_t keep_alive_timeout_sec, T callback) { -  assert(keep_alive_max_count > 0); -  auto ret = false; -  auto count = keep_alive_max_count; -  while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { -    auto close_connection = count == 1; -    auto connection_closed = false; -    ret = callback(close_connection, connection_closed); -    if (!ret || connection_closed) { break; } -    count--; -  } -  return ret; -} - -template <typename T> -inline bool -process_server_socket(socket_t sock, size_t keep_alive_max_count, -                      time_t keep_alive_timeout_sec, time_t read_timeout_sec, -                      time_t read_timeout_usec, time_t write_timeout_sec, -                      time_t write_timeout_usec, T callback) { -  return process_server_socket_core( -      sock, keep_alive_max_count, keep_alive_timeout_sec, -      [&](bool close_connection, bool &connection_closed) { -        SocketStream strm(sock, read_timeout_sec, read_timeout_usec, -                          write_timeout_sec, write_timeout_usec); -        return callback(strm, close_connection, connection_closed); -      }); -} - -template <typename T> -inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, -                                  time_t read_timeout_usec, -                                  time_t write_timeout_sec, -                                  time_t write_timeout_usec, T callback) { -  SocketStream strm(sock, read_timeout_sec, read_timeout_usec, -                    write_timeout_sec, write_timeout_usec); -  return callback(strm); -} - -inline int shutdown_socket(socket_t sock) { -#ifdef _WIN32 -  return shutdown(sock, SD_BOTH); -#else -  return shutdown(sock, SHUT_RDWR); -#endif -} - -template <typename BindOrConnect> -socket_t create_socket(const char *host, int port, int socket_flags, -                       bool tcp_nodelay, SocketOptions socket_options, -                       BindOrConnect bind_or_connect) { -  // Get address info -  struct addrinfo hints; -  struct addrinfo *result; - -  memset(&hints, 0, sizeof(struct addrinfo)); -  hints.ai_family = AF_UNSPEC; -  hints.ai_socktype = SOCK_STREAM; -  hints.ai_flags = socket_flags; -  hints.ai_protocol = 0; - -  auto service = std::to_string(port); - -  if (getaddrinfo(host, service.c_str(), &hints, &result)) { -#ifdef __linux__ -    res_init(); -#endif -    return INVALID_SOCKET; -  } - -  for (auto rp = result; rp; rp = rp->ai_next) { -    // Create a socket -#ifdef _WIN32 -    auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, -                           nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); -    /** -     * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 -     * and above the socket creation fails on older Windows Systems. -     * -     * Let's try to create a socket the old way in this case. -     * -     * Reference: -     * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa -     * -     * WSA_FLAG_NO_HANDLE_INHERIT: -     * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with -     * SP1, and later -     * -     */ -    if (sock == INVALID_SOCKET) { -      sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); -    } -#else -    auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); -#endif -    if (sock == INVALID_SOCKET) { continue; } - -#ifndef _WIN32 -    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } -#endif - -    if (tcp_nodelay) { -      int yes = 1; -      setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes), -                 sizeof(yes)); -    } - -    if (socket_options) { socket_options(sock); } - -    if (rp->ai_family == AF_INET6) { -      int no = 0; -      setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no), -                 sizeof(no)); -    } - -    // bind or connect -    if (bind_or_connect(sock, *rp)) { -      freeaddrinfo(result); -      return sock; -    } - -    close_socket(sock); -  } - -  freeaddrinfo(result); -  return INVALID_SOCKET; -} - -inline void set_nonblocking(socket_t sock, bool nonblocking) { -#ifdef _WIN32 -  auto flags = nonblocking ? 1UL : 0UL; -  ioctlsocket(sock, FIONBIO, &flags); -#else -  auto flags = fcntl(sock, F_GETFL, 0); -  fcntl(sock, F_SETFL, -        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); -#endif -} - -inline bool is_connection_error() { -#ifdef _WIN32 -  return WSAGetLastError() != WSAEWOULDBLOCK; -#else -  return errno != EINPROGRESS; -#endif -} - -inline bool bind_ip_address(socket_t sock, const char *host) { -  struct addrinfo hints; -  struct addrinfo *result; - -  memset(&hints, 0, sizeof(struct addrinfo)); -  hints.ai_family = AF_UNSPEC; -  hints.ai_socktype = SOCK_STREAM; -  hints.ai_protocol = 0; - -  if (getaddrinfo(host, "0", &hints, &result)) { return false; } - -  auto ret = false; -  for (auto rp = result; rp; rp = rp->ai_next) { -    const auto &ai = *rp; -    if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) { -      ret = true; -      break; -    } -  } - -  freeaddrinfo(result); -  return ret; -} - -#if !defined _WIN32 && !defined ANDROID -#define USE_IF2IP -#endif - -#ifdef USE_IF2IP -inline std::string if2ip(const std::string &ifn) { -  struct ifaddrs *ifap; -  getifaddrs(&ifap); -  for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { -    if (ifa->ifa_addr && ifn == ifa->ifa_name) { -      if (ifa->ifa_addr->sa_family == AF_INET) { -        auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr); -        char buf[INET_ADDRSTRLEN]; -        if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { -          freeifaddrs(ifap); -          return std::string(buf, INET_ADDRSTRLEN); -        } -      } -    } -  } -  freeifaddrs(ifap); -  return std::string(); -} -#endif - -inline socket_t create_client_socket(const char *host, int port, -                                     bool tcp_nodelay, -                                     SocketOptions socket_options, -                                     time_t timeout_sec, time_t timeout_usec, -                                     const std::string &intf, std::atomic<Error> &error) { -  auto sock = create_socket( -      host, port, 0, tcp_nodelay, std::move(socket_options), -      [&](socket_t sock, struct addrinfo &ai) -> bool { -        if (!intf.empty()) { -#ifdef USE_IF2IP -          auto ip = if2ip(intf); -          if (ip.empty()) { ip = intf; } -          if (!bind_ip_address(sock, ip.c_str())) { -            error = Error::BindIPAddress; -            return false; -          } -#endif -        } - -        set_nonblocking(sock, true); - -        auto ret = -            ::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen)); - -        if (ret < 0) { -          if (is_connection_error() || -              !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { -            close_socket(sock); -            error = Error::Connection; -            return false; -          } -        } - -        set_nonblocking(sock, false); -        error = Error::Success; -        return true; -      }); - -  if (sock != INVALID_SOCKET) { -    error = Error::Success; -  } else { -    if (error == Error::Success) { error = Error::Connection; } -  } - -  return sock; -} - -inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, -                                   socklen_t addr_len, std::string &ip, -                                   int &port) { -  if (addr.ss_family == AF_INET) { -    port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port); -  } else if (addr.ss_family == AF_INET6) { -    port = -        ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port); -  } - -  std::array<char, NI_MAXHOST> ipstr{}; -  if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len, -                   ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr, -                   0, NI_NUMERICHOST)) { -    ip = ipstr.data(); -  } -} - -inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { -  struct sockaddr_storage addr; -  socklen_t addr_len = sizeof(addr); - -  if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), -                   &addr_len)) { -    get_remote_ip_and_port(addr, addr_len, ip, port); -  } -} - -inline const char * -find_content_type(const std::string &path, -                  const std::map<std::string, std::string> &user_data) { -  auto ext = file_extension(path); - -  auto it = user_data.find(ext); -  if (it != user_data.end()) { return it->second.c_str(); } - -  if (ext == "txt") { -    return "text/plain"; -  } else if (ext == "html" || ext == "htm") { -    return "text/html"; -  } else if (ext == "css") { -    return "text/css"; -  } else if (ext == "jpeg" || ext == "jpg") { -    return "image/jpg"; -  } else if (ext == "png") { -    return "image/png"; -  } else if (ext == "gif") { -    return "image/gif"; -  } else if (ext == "svg") { -    return "image/svg+xml"; -  } else if (ext == "ico") { -    return "image/x-icon"; -  } else if (ext == "json") { -    return "application/json"; -  } else if (ext == "pdf") { -    return "application/pdf"; -  } else if (ext == "js") { -    return "application/javascript"; -  } else if (ext == "wasm") { -    return "application/wasm"; -  } else if (ext == "xml") { -    return "application/xml"; -  } else if (ext == "xhtml") { -    return "application/xhtml+xml"; -  } -  return nullptr; -} - -inline const char *status_message(int status) { -  switch (status) { -  case 100: return "Continue"; -  case 101: return "Switching Protocol"; -  case 102: return "Processing"; -  case 103: return "Early Hints"; -  case 200: return "OK"; -  case 201: return "Created"; -  case 202: return "Accepted"; -  case 203: return "Non-Authoritative Information"; -  case 204: return "No Content"; -  case 205: return "Reset Content"; -  case 206: return "Partial Content"; -  case 207: return "Multi-Status"; -  case 208: return "Already Reported"; -  case 226: return "IM Used"; -  case 300: return "Multiple Choice"; -  case 301: return "Moved Permanently"; -  case 302: return "Found"; -  case 303: return "See Other"; -  case 304: return "Not Modified"; -  case 305: return "Use Proxy"; -  case 306: return "unused"; -  case 307: return "Temporary Redirect"; -  case 308: return "Permanent Redirect"; -  case 400: return "Bad Request"; -  case 401: return "Unauthorized"; -  case 402: return "Payment Required"; -  case 403: return "Forbidden"; -  case 404: return "Not Found"; -  case 405: return "Method Not Allowed"; -  case 406: return "Not Acceptable"; -  case 407: return "Proxy Authentication Required"; -  case 408: return "Request Timeout"; -  case 409: return "Conflict"; -  case 410: return "Gone"; -  case 411: return "Length Required"; -  case 412: return "Precondition Failed"; -  case 413: return "Payload Too Large"; -  case 414: return "URI Too Long"; -  case 415: return "Unsupported Media Type"; -  case 416: return "Range Not Satisfiable"; -  case 417: return "Expectation Failed"; -  case 418: return "I'm a teapot"; -  case 421: return "Misdirected Request"; -  case 422: return "Unprocessable Entity"; -  case 423: return "Locked"; -  case 424: return "Failed Dependency"; -  case 425: return "Too Early"; -  case 426: return "Upgrade Required"; -  case 428: return "Precondition Required"; -  case 429: return "Too Many Requests"; -  case 431: return "Request Header Fields Too Large"; -  case 451: return "Unavailable For Legal Reasons"; -  case 501: return "Not Implemented"; -  case 502: return "Bad Gateway"; -  case 503: return "Service Unavailable"; -  case 504: return "Gateway Timeout"; -  case 505: return "HTTP Version Not Supported"; -  case 506: return "Variant Also Negotiates"; -  case 507: return "Insufficient Storage"; -  case 508: return "Loop Detected"; -  case 510: return "Not Extended"; -  case 511: return "Network Authentication Required"; - -  default: -  case 500: return "Internal Server Error"; -  } -} - -inline bool can_compress_content_type(const std::string &content_type) { -  return (!content_type.find("text/") && content_type != "text/event-stream") || -         content_type == "image/svg+xml" || -         content_type == "application/javascript" || -         content_type == "application/json" || -         content_type == "application/xml" || -         content_type == "application/xhtml+xml"; -} - -enum class EncodingType { None = 0, Gzip, Brotli }; - -inline EncodingType encoding_type(const Request &req, const Response &res) { -  auto ret = -      detail::can_compress_content_type(res.get_header_value("Content-Type")); -  if (!ret) { return EncodingType::None; } - -  const auto &s = req.get_header_value("Accept-Encoding"); -  (void)(s); - -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -  // TODO: 'Accept-Encoding' has br, not br;q=0 -  ret = s.find("br") != std::string::npos; -  if (ret) { return EncodingType::Brotli; } -#endif - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -  // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 -  ret = s.find("gzip") != std::string::npos; -  if (ret) { return EncodingType::Gzip; } -#endif - -  return EncodingType::None; -} - -class compressor { -public: -  virtual ~compressor(){}; - -  typedef std::function<bool(const char *data, size_t data_len)> Callback; -  virtual bool compress(const char *data, size_t data_length, bool last, -                        Callback callback) = 0; -}; - -class decompressor { -public: -  virtual ~decompressor() {} - -  virtual bool is_valid() const = 0; - -  typedef std::function<bool(const char *data, size_t data_len)> Callback; -  virtual bool decompress(const char *data, size_t data_length, -                          Callback callback) = 0; -}; - -class nocompressor : public compressor { -public: -  ~nocompressor(){}; - -  bool compress(const char *data, size_t data_length, bool /*last*/, -                Callback callback) override { -    if (!data_length) { return true; } -    return callback(data, data_length); -  } -}; - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -class gzip_compressor : public compressor { -public: -  gzip_compressor() { -    std::memset(&strm_, 0, sizeof(strm_)); -    strm_.zalloc = Z_NULL; -    strm_.zfree = Z_NULL; -    strm_.opaque = Z_NULL; - -    is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, -                             Z_DEFAULT_STRATEGY) == Z_OK; -  } - -  ~gzip_compressor() { deflateEnd(&strm_); } - -  bool compress(const char *data, size_t data_length, bool last, -                Callback callback) override { -    assert(is_valid_); - -    auto flush = last ? Z_FINISH : Z_NO_FLUSH; - -    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length); -    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data)); - -    int ret = Z_OK; - -    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; -    do { -      strm_.avail_out = buff.size(); -      strm_.next_out = reinterpret_cast<Bytef *>(buff.data()); - -      ret = deflate(&strm_, flush); -      assert(ret != Z_STREAM_ERROR); - -      if (!callback(buff.data(), buff.size() - strm_.avail_out)) { -        return false; -      } -    } while (strm_.avail_out == 0); - -    assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); -    assert(strm_.avail_in == 0); -    return true; -  } - -private: -  bool is_valid_ = false; -  z_stream strm_; -}; - -class gzip_decompressor : public decompressor { -public: -  gzip_decompressor() { -    std::memset(&strm_, 0, sizeof(strm_)); -    strm_.zalloc = Z_NULL; -    strm_.zfree = Z_NULL; -    strm_.opaque = Z_NULL; - -    // 15 is the value of wbits, which should be at the maximum possible value -    // to ensure that any gzip stream can be decoded. The offset of 32 specifies -    // that the stream type should be automatically detected either gzip or -    // deflate. -    is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; -  } - -  ~gzip_decompressor() { inflateEnd(&strm_); } - -  bool is_valid() const override { return is_valid_; } - -  bool decompress(const char *data, size_t data_length, -                  Callback callback) override { -    assert(is_valid_); - -    int ret = Z_OK; - -    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length); -    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data)); - -    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; -    while (strm_.avail_in > 0) { -      strm_.avail_out = buff.size(); -      strm_.next_out = reinterpret_cast<Bytef *>(buff.data()); - -      ret = inflate(&strm_, Z_NO_FLUSH); -      assert(ret != Z_STREAM_ERROR); -      switch (ret) { -      case Z_NEED_DICT: -      case Z_DATA_ERROR: -      case Z_MEM_ERROR: inflateEnd(&strm_); return false; -      } - -      if (!callback(buff.data(), buff.size() - strm_.avail_out)) { -        return false; -      } -    } - -    return ret == Z_OK || ret == Z_STREAM_END; -  } - -private: -  bool is_valid_ = false; -  z_stream strm_; -}; -#endif - -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -class brotli_compressor : public compressor { -public: -  brotli_compressor() { -    state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); -  } - -  ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } - -  bool compress(const char *data, size_t data_length, bool last, -                Callback callback) override { -    std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; - -    auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; -    auto available_in = data_length; -    auto next_in = reinterpret_cast<const uint8_t *>(data); - -    for (;;) { -      if (last) { -        if (BrotliEncoderIsFinished(state_)) { break; } -      } else { -        if (!available_in) { break; } -      } - -      auto available_out = buff.size(); -      auto next_out = buff.data(); - -      if (!BrotliEncoderCompressStream(state_, operation, &available_in, -                                       &next_in, &available_out, &next_out, -                                       nullptr)) { -        return false; -      } - -      auto output_bytes = buff.size() - available_out; -      if (output_bytes) { -        callback(reinterpret_cast<const char *>(buff.data()), output_bytes); -      } -    } - -    return true; -  } - -private: -  BrotliEncoderState *state_ = nullptr; -}; - -class brotli_decompressor : public decompressor { -public: -  brotli_decompressor() { -    decoder_s = BrotliDecoderCreateInstance(0, 0, 0); -    decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT -                          : BROTLI_DECODER_RESULT_ERROR; -  } - -  ~brotli_decompressor() { -    if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } -  } - -  bool is_valid() const override { return decoder_s; } - -  bool decompress(const char *data, size_t data_length, -                  Callback callback) override { -    if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || -        decoder_r == BROTLI_DECODER_RESULT_ERROR) { -      return 0; -    } - -    const uint8_t *next_in = (const uint8_t *)data; -    size_t avail_in = data_length; -    size_t total_out; - -    decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; - -    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; -    while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { -      char *next_out = buff.data(); -      size_t avail_out = buff.size(); - -      decoder_r = BrotliDecoderDecompressStream( -          decoder_s, &avail_in, &next_in, &avail_out, -          reinterpret_cast<uint8_t **>(&next_out), &total_out); - -      if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } - -      if (!callback(buff.data(), buff.size() - avail_out)) { return false; } -    } - -    return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || -           decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; -  } - -private: -  BrotliDecoderResult decoder_r; -  BrotliDecoderState *decoder_s = nullptr; -}; -#endif - -inline bool has_header(const Headers &headers, const char *key) { -  return headers.find(key) != headers.end(); -} - -inline const char *get_header_value(const Headers &headers, const char *key, -                                    size_t id = 0, const char *def = nullptr) { -  auto rng = headers.equal_range(key); -  auto it = rng.first; -  std::advance(it, static_cast<ssize_t>(id)); -  if (it != rng.second) { return it->second.c_str(); } -  return def; -} - -template <typename T> -inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, -                          size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} - -template <> -inline uint64_t get_header_value<uint64_t>(const Headers &headers, -                                           const char *key, size_t id, -                                           uint64_t def) { -  auto rng = headers.equal_range(key); -  auto it = rng.first; -  std::advance(it, static_cast<ssize_t>(id)); -  if (it != rng.second) { -    return std::strtoull(it->second.data(), nullptr, 10); -  } -  return def; -} - -template <typename T> -inline bool parse_header(const char *beg, const char *end, T fn) { -  // Skip trailing spaces and tabs. -  while (beg < end && is_space_or_tab(end[-1])) { -    end--; -  } - -  auto p = beg; -  while (p < end && *p != ':') { -    p++; -  } - -  if (p == end) { return false; } - -  auto key_end = p; - -  if (*p++ != ':') { return false; } - -  while (p < end && is_space_or_tab(*p)) { -    p++; -  } - -  if (p < end) { -    fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); -    return true; -  } - -  return false; -} - -inline bool read_headers(Stream &strm, Headers &headers) { -  const auto bufsiz = 2048; -  char buf[bufsiz]; -  stream_line_reader line_reader(strm, buf, bufsiz); - -  for (;;) { -    if (!line_reader.getline()) { return false; } - -    // Check if the line ends with CRLF. -    if (line_reader.end_with_crlf()) { -      // Blank line indicates end of headers. -      if (line_reader.size() == 2) { break; } -    } else { -      continue; // Skip invalid line. -    } - -    // Exclude CRLF -    auto end = line_reader.ptr() + line_reader.size() - 2; - -    parse_header(line_reader.ptr(), end, -                 [&](std::string &&key, std::string &&val) { -                   headers.emplace(std::move(key), std::move(val)); -                 }); -  } - -  return true; -} - -inline bool read_content_with_length(Stream &strm, uint64_t len, -                                     Progress progress, -                                     ContentReceiverWithProgress out) { -  char buf[CPPHTTPLIB_RECV_BUFSIZ]; - -  uint64_t r = 0; -  while (r < len) { -    auto read_len = static_cast<size_t>(len - r); -    auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); -    if (n <= 0) { return false; } - -    if (!out(buf, static_cast<size_t>(n), r, len)) { return false; } -    r += static_cast<uint64_t>(n); - -    if (progress) { -      if (!progress(r, len)) { return false; } -    } -  } - -  return true; -} - -inline void skip_content_with_length(Stream &strm, uint64_t len) { -  char buf[CPPHTTPLIB_RECV_BUFSIZ]; -  uint64_t r = 0; -  while (r < len) { -    auto read_len = static_cast<size_t>(len - r); -    auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); -    if (n <= 0) { return; } -    r += static_cast<uint64_t>(n); -  } -} - -inline bool read_content_without_length(Stream &strm, -                                        ContentReceiverWithProgress out) { -  char buf[CPPHTTPLIB_RECV_BUFSIZ]; -  uint64_t r = 0; -  for (;;) { -    auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); -    if (n < 0) { -      return false; -    } else if (n == 0) { -      return true; -    } - -    if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; } -    r += static_cast<uint64_t>(n); -  } - -  return true; -} - -inline bool read_content_chunked(Stream &strm, -                                 ContentReceiverWithProgress out) { -  const auto bufsiz = 16; -  char buf[bufsiz]; - -  stream_line_reader line_reader(strm, buf, bufsiz); - -  if (!line_reader.getline()) { return false; } - -  unsigned long chunk_len; -  while (true) { -    char *end_ptr; - -    chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); - -    if (end_ptr == line_reader.ptr()) { return false; } -    if (chunk_len == ULONG_MAX) { return false; } - -    if (chunk_len == 0) { break; } - -    if (!read_content_with_length(strm, chunk_len, nullptr, out)) { -      return false; -    } - -    if (!line_reader.getline()) { return false; } - -    if (strcmp(line_reader.ptr(), "\r\n")) { break; } - -    if (!line_reader.getline()) { return false; } -  } - -  if (chunk_len == 0) { -    // Reader terminator after chunks -    if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) -      return false; -  } - -  return true; -} - -inline bool is_chunked_transfer_encoding(const Headers &headers) { -  return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), -                     "chunked"); -} - -template <typename T, typename U> -bool prepare_content_receiver(T &x, int &status, -                              ContentReceiverWithProgress receiver, -                              bool decompress, U callback) { -  if (decompress) { -    std::string encoding = x.get_header_value("Content-Encoding"); -    std::unique_ptr<decompressor> decompressor; - -    if (encoding.find("gzip") != std::string::npos || -        encoding.find("deflate") != std::string::npos) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -      decompressor = detail::make_unique<gzip_decompressor>(); -#else -      status = 415; -      return false; -#endif -    } else if (encoding.find("br") != std::string::npos) { -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -      decompressor = detail::make_unique<brotli_decompressor>(); -#else -      status = 415; -      return false; -#endif -    } - -    if (decompressor) { -      if (decompressor->is_valid()) { -        ContentReceiverWithProgress out = [&](const char *buf, size_t n, -                                              uint64_t off, uint64_t len) { -          return decompressor->decompress(buf, n, -                                          [&](const char *buf, size_t n) { -                                            return receiver(buf, n, off, len); -                                          }); -        }; -        return callback(std::move(out)); -      } else { -        status = 500; -        return false; -      } -    } -  } - -  ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, -                                        uint64_t len) { -    return receiver(buf, n, off, len); -  }; -  return callback(std::move(out)); -} - -template <typename T> -bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, -                  Progress progress, ContentReceiverWithProgress receiver, -                  bool decompress) { -  return prepare_content_receiver( -      x, status, std::move(receiver), decompress, -      [&](const ContentReceiverWithProgress &out) { -        auto ret = true; -        auto exceed_payload_max_length = false; - -        if (is_chunked_transfer_encoding(x.headers)) { -          ret = read_content_chunked(strm, out); -        } else if (!has_header(x.headers, "Content-Length")) { -          ret = read_content_without_length(strm, out); -        } else { -          auto len = get_header_value<uint64_t>(x.headers, "Content-Length"); -          if (len > payload_max_length) { -            exceed_payload_max_length = true; -            skip_content_with_length(strm, len); -            ret = false; -          } else if (len > 0) { -            ret = read_content_with_length(strm, len, std::move(progress), out); -          } -        } - -        if (!ret) { status = exceed_payload_max_length ? 413 : 400; } -        return ret; -      }); -} - -template <typename T> -inline ssize_t write_headers(Stream &strm, const T &info, -                             const Headers &headers) { -  ssize_t write_len = 0; -  for (const auto &x : info.headers) { -    if (x.first == "EXCEPTION_WHAT") { continue; } -    auto len = -        strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); -    if (len < 0) { return len; } -    write_len += len; -  } -  for (const auto &x : headers) { -    auto len = -        strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); -    if (len < 0) { return len; } -    write_len += len; -  } -  auto len = strm.write("\r\n"); -  if (len < 0) { return len; } -  write_len += len; -  return write_len; -} - -inline bool write_data(Stream &strm, const char *d, size_t l) { -  size_t offset = 0; -  while (offset < l) { -    auto length = strm.write(d + offset, l - offset); -    if (length < 0) { return false; } -    offset += static_cast<size_t>(length); -  } -  return true; -} - -template <typename T> -inline ssize_t write_content(Stream &strm, ContentProvider content_provider, -                             size_t offset, size_t length, T is_shutting_down) { -  size_t begin_offset = offset; -  size_t end_offset = offset + length; -  auto ok = true; -  DataSink data_sink; - -  data_sink.write = [&](const char *d, size_t l) { -    if (ok) { -      offset += l; -      if (!write_data(strm, d, l)) { ok = false; } -    } -  }; - -  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - -  while (offset < end_offset && !is_shutting_down()) { -    if (!content_provider(offset, end_offset - offset, data_sink)) { -      return -1; -    } -    if (!ok) { return -1; } -  } - -  return static_cast<ssize_t>(offset - begin_offset); -} - -template <typename T> -inline ssize_t write_content_without_length(Stream &strm, -                                            ContentProvider content_provider, -                                            T is_shutting_down) { -  size_t offset = 0; -  auto data_available = true; -  auto ok = true; -  DataSink data_sink; - -  data_sink.write = [&](const char *d, size_t l) { -    if (ok) { -      offset += l; -      if (!write_data(strm, d, l)) { ok = false; } -    } -  }; - -  data_sink.done = [&](void) { data_available = false; }; - -  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - -  while (data_available && !is_shutting_down()) { -    if (!content_provider(offset, 0, data_sink)) { return -1; } -    if (!ok) { return -1; } -  } - -  return static_cast<ssize_t>(offset); -} - -template <typename T, typename U> -inline ssize_t write_content_chunked(Stream &strm, -                                     ContentProvider content_provider, -                                     T is_shutting_down, U &compressor) { -  size_t offset = 0; -  auto data_available = true; -  ssize_t total_written_length = 0; -  auto ok = true; -  DataSink data_sink; - -  data_sink.write = [&](const char *d, size_t l) { -    if (!ok) { return; } - -    data_available = l > 0; -    offset += l; - -    std::string payload; -    if (!compressor.compress(d, l, false, -                             [&](const char *data, size_t data_len) { -                               payload.append(data, data_len); -                               return true; -                             })) { -      ok = false; -      return; -    } - -    if (!payload.empty()) { -      // Emit chunked response header and footer for each chunk -      auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; -      if (write_data(strm, chunk.data(), chunk.size())) { -        total_written_length += chunk.size(); -      } else { -        ok = false; -        return; -      } -    } -  }; - -  data_sink.done = [&](void) { -    if (!ok) { return; } - -    data_available = false; - -    std::string payload; -    if (!compressor.compress(nullptr, 0, true, -                             [&](const char *data, size_t data_len) { -                               payload.append(data, data_len); -                               return true; -                             })) { -      ok = false; -      return; -    } - -    if (!payload.empty()) { -      // Emit chunked response header and footer for each chunk -      auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; -      if (write_data(strm, chunk.data(), chunk.size())) { -        total_written_length += chunk.size(); -      } else { -        ok = false; -        return; -      } -    } - -    static const std::string done_marker("0\r\n\r\n"); -    if (write_data(strm, done_marker.data(), done_marker.size())) { -      total_written_length += done_marker.size(); -    } else { -      ok = false; -    } -  }; - -  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - -  while (data_available && !is_shutting_down()) { -    if (!content_provider(offset, 0, data_sink)) { return -1; } -    if (!ok) { return -1; } -  } - -  return total_written_length; -} - -template <typename T> -inline bool redirect(T &cli, const Request &req, Response &res, -                     const std::string &path) { -  Request new_req = req; -  new_req.path = path; -  new_req.redirect_count -= 1; - -  if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { -    new_req.method = "GET"; -    new_req.body.clear(); -    new_req.headers.clear(); -  } - -  Response new_res; - -  auto ret = cli.send(new_req, new_res); -  if (ret) { res = new_res; } -  return ret; -} - -inline std::string params_to_query_str(const Params ¶ms) { -  std::string query; - -  for (auto it = params.begin(); it != params.end(); ++it) { -    if (it != params.begin()) { query += "&"; } -    query += it->first; -    query += "="; -    query += encode_url(it->second); -  } -  return query; -} - -inline void parse_query_text(const std::string &s, Params ¶ms) { -  split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { -    std::string key; -    std::string val; -    split(b, e, '=', [&](const char *b2, const char *e2) { -      if (key.empty()) { -        key.assign(b2, e2); -      } else { -        val.assign(b2, e2); -      } -    }); - -    if (!key.empty()) { -      params.emplace(decode_url(key, true), decode_url(val, true)); -    } -  }); -} - -inline bool parse_multipart_boundary(const std::string &content_type, -                                     std::string &boundary) { -  auto pos = content_type.find("boundary="); -  if (pos == std::string::npos) { return false; } -  boundary = content_type.substr(pos + 9); -  if (boundary.length() >= 2 && boundary.front() == '"' && -      boundary.back() == '"') { -    boundary = boundary.substr(1, boundary.size() - 2); -  } -  return !boundary.empty(); -} - -inline bool parse_range_header(const std::string &s, Ranges &ranges) try { -  static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); -  std::smatch m; -  if (std::regex_match(s, m, re_first_range)) { -    auto pos = static_cast<size_t>(m.position(1)); -    auto len = static_cast<size_t>(m.length(1)); -    bool all_valid_ranges = true; -    split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { -      if (!all_valid_ranges) return; -      static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); -      std::cmatch cm; -      if (std::regex_match(b, e, cm, re_another_range)) { -        ssize_t first = -1; -        if (!cm.str(1).empty()) { -          first = static_cast<ssize_t>(std::stoll(cm.str(1))); -        } - -        ssize_t last = -1; -        if (!cm.str(2).empty()) { -          last = static_cast<ssize_t>(std::stoll(cm.str(2))); -        } - -        if (first != -1 && last != -1 && first > last) { -          all_valid_ranges = false; -          return; -        } -        ranges.emplace_back(std::make_pair(first, last)); -      } -    }); -    return all_valid_ranges; -  } -  return false; -} catch (...) { return false; } - -class MultipartFormDataParser { -public: -  MultipartFormDataParser() = default; - -  void set_boundary(std::string &&boundary) { boundary_ = boundary; } - -  bool is_valid() const { return is_valid_; } - -  template <typename T, typename U> -  bool parse(const char *buf, size_t n, const T &content_callback, -             const U &header_callback) { - -    static const std::regex re_content_disposition( -        "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" -        "\"(.*?)\")?\\s*$", -        std::regex_constants::icase); -    static const std::string dash_ = "--"; -    static const std::string crlf_ = "\r\n"; - -    buf_.append(buf, n); // TODO: performance improvement - -    while (!buf_.empty()) { -      switch (state_) { -      case 0: { // Initial boundary -        auto pattern = dash_ + boundary_ + crlf_; -        if (pattern.size() > buf_.size()) { return true; } -        auto pos = buf_.find(pattern); -        if (pos != 0) { return false; } -        buf_.erase(0, pattern.size()); -        off_ += pattern.size(); -        state_ = 1; -        break; -      } -      case 1: { // New entry -        clear_file_info(); -        state_ = 2; -        break; -      } -      case 2: { // Headers -        auto pos = buf_.find(crlf_); -        while (pos != std::string::npos) { -          // Empty line -          if (pos == 0) { -            if (!header_callback(file_)) { -              is_valid_ = false; -              return false; -            } -            buf_.erase(0, crlf_.size()); -            off_ += crlf_.size(); -            state_ = 3; -            break; -          } - -          static const std::string header_name = "content-type:"; -          const auto header = buf_.substr(0, pos); -          if (start_with(header, header_name)) { -            file_.content_type = trim_copy(header.substr(header_name.size())); -          } else { -            std::smatch m; -            if (std::regex_match(header, m, re_content_disposition)) { -              file_.name = m[1]; -              file_.filename = m[2]; -            } -          } - -          buf_.erase(0, pos + crlf_.size()); -          off_ += pos + crlf_.size(); -          pos = buf_.find(crlf_); -        } -        if (state_ != 3) { return true; } -        break; -      } -      case 3: { // Body -        { -          auto pattern = crlf_ + dash_; -          if (pattern.size() > buf_.size()) { return true; } - -          auto pos = buf_.find(pattern); -          if (pos == std::string::npos) { -            pos = buf_.size(); -            while (pos > 0) { -              auto c = buf_[pos - 1]; -              if (c != '\r' && c != '\n' && c != '-') { break; } -              pos--; -            } -          } - -          if (!content_callback(buf_.data(), pos)) { -            is_valid_ = false; -            return false; -          } - -          off_ += pos; -          buf_.erase(0, pos); -        } - -        { -          auto pattern = crlf_ + dash_ + boundary_; -          if (pattern.size() > buf_.size()) { return true; } - -          auto pos = buf_.find(pattern); -          if (pos != std::string::npos) { -            if (!content_callback(buf_.data(), pos)) { -              is_valid_ = false; -              return false; -            } - -            off_ += pos + pattern.size(); -            buf_.erase(0, pos + pattern.size()); -            state_ = 4; -          } else { -            if (!content_callback(buf_.data(), pattern.size())) { -              is_valid_ = false; -              return false; -            } - -            off_ += pattern.size(); -            buf_.erase(0, pattern.size()); -          } -        } -        break; -      } -      case 4: { // Boundary -        if (crlf_.size() > buf_.size()) { return true; } -        if (buf_.compare(0, crlf_.size(), crlf_) == 0) { -          buf_.erase(0, crlf_.size()); -          off_ += crlf_.size(); -          state_ = 1; -        } else { -          auto pattern = dash_ + crlf_; -          if (pattern.size() > buf_.size()) { return true; } -          if (buf_.compare(0, pattern.size(), pattern) == 0) { -            buf_.erase(0, pattern.size()); -            off_ += pattern.size(); -            is_valid_ = true; -            state_ = 5; -          } else { -            return true; -          } -        } -        break; -      } -      case 5: { // Done -        is_valid_ = false; -        return false; -      } -      } -    } - -    return true; -  } - -private: -  void clear_file_info() { -    file_.name.clear(); -    file_.filename.clear(); -    file_.content_type.clear(); -  } - -  std::string boundary_; - -  std::string buf_; -  size_t state_ = 0; -  bool is_valid_ = false; -  size_t off_ = 0; -  MultipartFormData file_; -}; - -inline std::string to_lower(const char *beg, const char *end) { -  std::string out; -  auto it = beg; -  while (it != end) { -    out += static_cast<char>(::tolower(*it)); -    it++; -  } -  return out; -} - -inline std::string make_multipart_data_boundary() { -  static const char data[] = -      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -  // std::random_device might actually be deterministic on some -  // platforms, but due to lack of support in the c++ standard library, -  // doing better requires either some ugly hacks or breaking portability. -  std::random_device seed_gen; -  // Request 128 bits of entropy for initialization -  std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; -  std::mt19937 engine(seed_sequence); - -  std::string result = "--cpp-httplib-multipart-data-"; - -  for (auto i = 0; i < 16; i++) { -    result += data[engine() % (sizeof(data) - 1)]; -  } - -  return result; -} - -inline std::pair<size_t, size_t> -get_range_offset_and_length(const Request &req, size_t content_length, -                            size_t index) { -  auto r = req.ranges[index]; - -  if (r.first == -1 && r.second == -1) { -    return std::make_pair(0, content_length); -  } - -  auto slen = static_cast<ssize_t>(content_length); - -  if (r.first == -1) { -    r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second); -    r.second = slen - 1; -  } - -  if (r.second == -1) { r.second = slen - 1; } - -  return std::make_pair(r.first, r.second - r.first + 1); -} - -inline std::string make_content_range_header_field(size_t offset, size_t length, -                                                   size_t content_length) { -  std::string field = "bytes "; -  field += std::to_string(offset); -  field += "-"; -  field += std::to_string(offset + length - 1); -  field += "/"; -  field += std::to_string(content_length); -  return field; -} - -template <typename SToken, typename CToken, typename Content> -bool process_multipart_ranges_data(const Request &req, Response &res, -                                   const std::string &boundary, -                                   const std::string &content_type, -                                   SToken stoken, CToken ctoken, -                                   Content content) { -  for (size_t i = 0; i < req.ranges.size(); i++) { -    ctoken("--"); -    stoken(boundary); -    ctoken("\r\n"); -    if (!content_type.empty()) { -      ctoken("Content-Type: "); -      stoken(content_type); -      ctoken("\r\n"); -    } - -    auto offsets = get_range_offset_and_length(req, res.body.size(), i); -    auto offset = offsets.first; -    auto length = offsets.second; - -    ctoken("Content-Range: "); -    stoken(make_content_range_header_field(offset, length, res.body.size())); -    ctoken("\r\n"); -    ctoken("\r\n"); -    if (!content(offset, length)) { return false; } -    ctoken("\r\n"); -  } - -  ctoken("--"); -  stoken(boundary); -  ctoken("--\r\n"); - -  return true; -} - -inline std::string make_multipart_ranges_data(const Request &req, Response &res, -                                              const std::string &boundary, -                                              const std::string &content_type) { -  std::string data; - -  process_multipart_ranges_data( -      req, res, boundary, content_type, -      [&](const std::string &token) { data += token; }, -      [&](const char *token) { data += token; }, -      [&](size_t offset, size_t length) { -        data += res.body.substr(offset, length); -        return true; -      }); - -  return data; -} - -inline size_t -get_multipart_ranges_data_length(const Request &req, Response &res, -                                 const std::string &boundary, -                                 const std::string &content_type) { -  size_t data_length = 0; - -  process_multipart_ranges_data( -      req, res, boundary, content_type, -      [&](const std::string &token) { data_length += token.size(); }, -      [&](const char *token) { data_length += strlen(token); }, -      [&](size_t /*offset*/, size_t length) { -        data_length += length; -        return true; -      }); - -  return data_length; -} - -template <typename T> -inline bool write_multipart_ranges_data(Stream &strm, const Request &req, -                                        Response &res, -                                        const std::string &boundary, -                                        const std::string &content_type, -                                        T is_shutting_down) { -  return process_multipart_ranges_data( -      req, res, boundary, content_type, -      [&](const std::string &token) { strm.write(token); }, -      [&](const char *token) { strm.write(token); }, -      [&](size_t offset, size_t length) { -        return write_content(strm, res.content_provider_, offset, length, -                             is_shutting_down) >= 0; -      }); -} - -inline std::pair<size_t, size_t> -get_range_offset_and_length(const Request &req, const Response &res, -                            size_t index) { -  auto r = req.ranges[index]; - -  if (r.second == -1) { -    r.second = static_cast<ssize_t>(res.content_length_) - 1; -  } - -  return std::make_pair(r.first, r.second - r.first + 1); -} - -inline bool expect_content(const Request &req) { -  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || -      req.method == "PRI" || req.method == "DELETE") { -    return true; -  } -  // TODO: check if Content-Length is set -  return false; -} - -inline bool has_crlf(const char *s) { -  auto p = s; -  while (*p) { -    if (*p == '\r' || *p == '\n') { return true; } -    p++; -  } -  return false; -} - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -template <typename CTX, typename Init, typename Update, typename Final> -inline std::string message_digest(const std::string &s, Init init, -                                  Update update, Final final, -                                  size_t digest_length) { -  using namespace std; - -  std::vector<unsigned char> md(digest_length, 0); -  CTX ctx; -  init(&ctx); -  update(&ctx, s.data(), s.size()); -  final(md.data(), &ctx); - -  stringstream ss; -  for (auto c : md) { -    ss << setfill('0') << setw(2) << hex << (unsigned int)c; -  } -  return ss.str(); -} - -inline std::string MD5(const std::string &s) { -  return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final, -                                 MD5_DIGEST_LENGTH); -} - -inline std::string SHA_256(const std::string &s) { -  return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final, -                                    SHA256_DIGEST_LENGTH); -} - -inline std::string SHA_512(const std::string &s) { -  return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final, -                                    SHA512_DIGEST_LENGTH); -} -#endif - -#ifdef _WIN32 -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -// NOTE: This code came up with the following stackoverflow post: -// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store -inline bool load_system_certs_on_windows(X509_STORE *store) { -  auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); - -  if (!hStore) { return false; } - -  PCCERT_CONTEXT pContext = NULL; -  while (pContext = CertEnumCertificatesInStore(hStore, pContext)) { -    auto encoded_cert = -        static_cast<const unsigned char *>(pContext->pbCertEncoded); - -    auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); -    if (x509) { -      X509_STORE_add_cert(store, x509); -      X509_free(x509); -    } -  } - -  CertFreeCertificateContext(pContext); -  CertCloseStore(hStore, 0); - -  return true; -} -#endif - -class WSInit { -public: -  WSInit() { -    WSADATA wsaData; -    WSAStartup(0x0002, &wsaData); -  } - -  ~WSInit() { WSACleanup(); } -}; - -static WSInit wsinit_; -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline std::pair<std::string, std::string> make_digest_authentication_header( -    const Request &req, const std::map<std::string, std::string> &auth, -    size_t cnonce_count, const std::string &cnonce, const std::string &username, -    const std::string &password, bool is_proxy = false) { -  using namespace std; - -  string nc; -  { -    stringstream ss; -    ss << setfill('0') << setw(8) << hex << cnonce_count; -    nc = ss.str(); -  } - -  auto qop = auth.at("qop"); -  if (qop.find("auth-int") != std::string::npos) { -    qop = "auth-int"; -  } else { -    qop = "auth"; -  } - -  std::string algo = "MD5"; -  if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } - -  string response; -  { -    auto H = algo == "SHA-256" -                 ? detail::SHA_256 -                 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; - -    auto A1 = username + ":" + auth.at("realm") + ":" + password; - -    auto A2 = req.method + ":" + req.path; -    if (qop == "auth-int") { A2 += ":" + H(req.body); } - -    response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + -                 ":" + qop + ":" + H(A2)); -  } - -  auto field = "Digest username=\"" + username + "\", realm=\"" + -               auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + -               "\", uri=\"" + req.path + "\", algorithm=" + algo + -               ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + -               "\", response=\"" + response + "\""; - -  auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; -  return std::make_pair(key, field); -} -#endif - -inline bool parse_www_authenticate(const Response &res, -                                   std::map<std::string, std::string> &auth, -                                   bool is_proxy) { -  auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; -  if (res.has_header(auth_key)) { -    static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); -    auto s = res.get_header_value(auth_key); -    auto pos = s.find(' '); -    if (pos != std::string::npos) { -      auto type = s.substr(0, pos); -      if (type == "Basic") { -        return false; -      } else if (type == "Digest") { -        s = s.substr(pos + 1); -        auto beg = std::sregex_iterator(s.begin(), s.end(), re); -        for (auto i = beg; i != std::sregex_iterator(); ++i) { -          auto m = *i; -          auto key = s.substr(static_cast<size_t>(m.position(1)), -                              static_cast<size_t>(m.length(1))); -          auto val = m.length(2) > 0 -                         ? s.substr(static_cast<size_t>(m.position(2)), -                                    static_cast<size_t>(m.length(2))) -                         : s.substr(static_cast<size_t>(m.position(3)), -                                    static_cast<size_t>(m.length(3))); -          auth[key] = val; -        } -        return true; -      } -    } -  } -  return false; -} - -// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 -inline std::string random_string(size_t length) { -  auto randchar = []() -> char { -    const char charset[] = "0123456789" -                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -                           "abcdefghijklmnopqrstuvwxyz"; -    const size_t max_index = (sizeof(charset) - 1); -    return charset[static_cast<size_t>(rand()) % max_index]; -  }; -  std::string str(length, 0); -  std::generate_n(str.begin(), length, randchar); -  return str; -} - -class ContentProviderAdapter { -public: -  explicit ContentProviderAdapter( -      ContentProviderWithoutLength &&content_provider) -      : content_provider_(content_provider) {} - -  bool operator()(size_t offset, size_t, DataSink &sink) { -    return content_provider_(offset, sink); -  } - -private: -  ContentProviderWithoutLength content_provider_; -}; - -} // namespace detail - -// Header utilities -inline std::pair<std::string, std::string> make_range_header(Ranges ranges) { -  std::string field = "bytes="; -  auto i = 0; -  for (auto r : ranges) { -    if (i != 0) { field += ", "; } -    if (r.first != -1) { field += std::to_string(r.first); } -    field += '-'; -    if (r.second != -1) { field += std::to_string(r.second); } -    i++; -  } -  return std::make_pair("Range", std::move(field)); -} - -inline std::pair<std::string, std::string> -make_basic_authentication_header(const std::string &username, -                                 const std::string &password, -                                 bool is_proxy = false) { -  auto field = "Basic " + detail::base64_encode(username + ":" + password); -  auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; -  return std::make_pair(key, std::move(field)); -} - -inline std::pair<std::string, std::string> -make_bearer_token_authentication_header(const std::string &token, -                                        bool is_proxy = false) { -  auto field = "Bearer " + token; -  auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; -  return std::make_pair(key, std::move(field)); -} - -// Request implementation -inline bool Request::has_header(const char *key) const { -  return detail::has_header(headers, key); -} - -inline std::string Request::get_header_value(const char *key, size_t id) const { -  return detail::get_header_value(headers, key, id, ""); -} - -template <typename T> -inline T Request::get_header_value(const char *key, size_t id) const { -  return detail::get_header_value<T>(headers, key, id, 0); -} - -inline size_t Request::get_header_value_count(const char *key) const { -  auto r = headers.equal_range(key); -  return static_cast<size_t>(std::distance(r.first, r.second)); -} - -inline void Request::set_header(const char *key, const char *val) { -  if (!detail::has_crlf(key) && !detail::has_crlf(val)) { -    headers.emplace(key, val); -  } -} - -inline void Request::set_header(const char *key, const std::string &val) { -  if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { -    headers.emplace(key, val); -  } -} - -inline bool Request::has_param(const char *key) const { -  return params.find(key) != params.end(); -} - -inline std::string Request::get_param_value(const char *key, size_t id) const { -  auto rng = params.equal_range(key); -  auto it = rng.first; -  std::advance(it, static_cast<ssize_t>(id)); -  if (it != rng.second) { return it->second; } -  return std::string(); -} - -inline size_t Request::get_param_value_count(const char *key) const { -  auto r = params.equal_range(key); -  return static_cast<size_t>(std::distance(r.first, r.second)); -} - -inline bool Request::is_multipart_form_data() const { -  const auto &content_type = get_header_value("Content-Type"); -  return !content_type.find("multipart/form-data"); -} - -inline bool Request::has_file(const char *key) const { -  return files.find(key) != files.end(); -} - -inline MultipartFormData Request::get_file_value(const char *key) const { -  auto it = files.find(key); -  if (it != files.end()) { return it->second; } -  return MultipartFormData(); -} - -// Response implementation -inline bool Response::has_header(const char *key) const { -  return headers.find(key) != headers.end(); -} - -inline std::string Response::get_header_value(const char *key, -                                              size_t id) const { -  return detail::get_header_value(headers, key, id, ""); -} - -template <typename T> -inline T Response::get_header_value(const char *key, size_t id) const { -  return detail::get_header_value<T>(headers, key, id, 0); -} - -inline size_t Response::get_header_value_count(const char *key) const { -  auto r = headers.equal_range(key); -  return static_cast<size_t>(std::distance(r.first, r.second)); -} - -inline void Response::set_header(const char *key, const char *val) { -  if (!detail::has_crlf(key) && !detail::has_crlf(val)) { -    headers.emplace(key, val); -  } -} - -inline void Response::set_header(const char *key, const std::string &val) { -  if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { -    headers.emplace(key, val); -  } -} - -inline void Response::set_redirect(const char *url, int stat) { -  if (!detail::has_crlf(url)) { -    set_header("Location", url); -    if (300 <= stat && stat < 400) { -      this->status = stat; -    } else { -      this->status = 302; -    } -  } -} - -inline void Response::set_redirect(const std::string &url, int stat) { -  set_redirect(url.c_str(), stat); -} - -inline void Response::set_content(const char *s, size_t n, -                                  const char *content_type) { -  body.assign(s, n); -  set_header("Content-Type", content_type); -} - -inline void Response::set_content(std::string s, const char *content_type) { -  body = std::move(s); -  set_header("Content-Type", content_type); -} - -inline void -Response::set_content_provider(size_t in_length, const char *content_type, -                               ContentProvider provider, -                               const std::function<void()> &resource_releaser) { -  assert(in_length > 0); -  set_header("Content-Type", content_type); -  content_length_ = in_length; -  content_provider_ = std::move(provider); -  content_provider_resource_releaser_ = resource_releaser; -  is_chunked_content_provider = false; -} - -inline void -Response::set_content_provider(const char *content_type, -                               ContentProviderWithoutLength provider, -                               const std::function<void()> &resource_releaser) { -  set_header("Content-Type", content_type); -  content_length_ = 0; -  content_provider_ = detail::ContentProviderAdapter(std::move(provider)); -  content_provider_resource_releaser_ = resource_releaser; -  is_chunked_content_provider = false; -} - -inline void Response::set_chunked_content_provider( -    const char *content_type, ContentProviderWithoutLength provider, -    const std::function<void()> &resource_releaser) { -  set_header("Content-Type", content_type); -  content_length_ = 0; -  content_provider_ = detail::ContentProviderAdapter(std::move(provider)); -  content_provider_resource_releaser_ = resource_releaser; -  is_chunked_content_provider = true; -} - -// Rstream implementation -inline ssize_t Stream::write(const char *ptr) { -  return write(ptr, strlen(ptr)); -} - -inline ssize_t Stream::write(const std::string &s) { -  return write(s.data(), s.size()); -} - -template <typename... Args> -inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { -  const auto bufsiz = 2048; -  std::array<char, bufsiz> buf; - -#if defined(_MSC_VER) && _MSC_VER < 1900 -  auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); -#else -  auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); -#endif -  if (sn <= 0) { return sn; } - -  auto n = static_cast<size_t>(sn); - -  if (n >= buf.size() - 1) { -    std::vector<char> glowable_buf(buf.size()); - -    while (n >= glowable_buf.size() - 1) { -      glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 -      n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(), -                                          glowable_buf.size() - 1, fmt, -                                          args...)); -#else -      n = static_cast<size_t>( -          snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); -#endif -    } -    return write(&glowable_buf[0], n); -  } else { -    return write(buf.data(), n); -  } -} - -namespace detail { - -// Socket stream implementation -inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, -                                  time_t read_timeout_usec, -                                  time_t write_timeout_sec, -                                  time_t write_timeout_usec) -    : sock_(sock), read_timeout_sec_(read_timeout_sec), -      read_timeout_usec_(read_timeout_usec), -      write_timeout_sec_(write_timeout_sec), -      write_timeout_usec_(write_timeout_usec) {} - -inline SocketStream::~SocketStream() {} - -inline bool SocketStream::is_readable() const { -  return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; -} - -inline bool SocketStream::is_writable() const { -  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; -} - -inline ssize_t SocketStream::read(char *ptr, size_t size) { -  if (!is_readable()) { return -1; } - -#ifdef _WIN32 -  if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) { -    return -1; -  } -  return recv(sock_, ptr, static_cast<int>(size), 0); -#else -  return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); }); -#endif -} - -inline ssize_t SocketStream::write(const char *ptr, size_t size) { -  if (!is_writable()) { return -1; } - -#ifdef _WIN32 -  if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) { -    return -1; -  } -  return send(sock_, ptr, static_cast<int>(size), 0); -#else -  return handle_EINTR([&]() { return send(sock_, ptr, size, 0); }); -#endif -} - -inline void SocketStream::get_remote_ip_and_port(std::string &ip, -                                                 int &port) const { -  return detail::get_remote_ip_and_port(sock_, ip, port); -} - -// Buffer stream implementation -inline bool BufferStream::is_readable() const { return true; } - -inline bool BufferStream::is_writable() const { return true; } - -inline ssize_t BufferStream::read(char *ptr, size_t size) { -#if defined(_MSC_VER) && _MSC_VER <= 1900 -  auto len_read = buffer._Copy_s(ptr, size, size, position); -#else -  auto len_read = buffer.copy(ptr, size, position); -#endif -  position += static_cast<size_t>(len_read); -  return static_cast<ssize_t>(len_read); -} - -inline ssize_t BufferStream::write(const char *ptr, size_t size) { -  buffer.append(ptr, size); -  return static_cast<ssize_t>(size); -} - -inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, -                                                 int & /*port*/) const {} - -inline const std::string &BufferStream::get_buffer() const { return buffer; } - -} // namespace detail - -// HTTP server implementation -inline Server::Server() -    : new_task_queue( -          [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), -      svr_sock_(INVALID_SOCKET), is_running_(false) { -#ifndef _WIN32 -  signal(SIGPIPE, SIG_IGN); -#endif -} - -inline Server::~Server() {} - -inline Server &Server::Get(const char *pattern, Handler handler) { -  get_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Post(const char *pattern, Handler handler) { -  post_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Post(const char *pattern, -                            HandlerWithContentReader handler) { -  post_handlers_for_content_reader_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Put(const char *pattern, Handler handler) { -  put_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Put(const char *pattern, -                           HandlerWithContentReader handler) { -  put_handlers_for_content_reader_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Patch(const char *pattern, Handler handler) { -  patch_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Patch(const char *pattern, -                             HandlerWithContentReader handler) { -  patch_handlers_for_content_reader_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Delete(const char *pattern, Handler handler) { -  delete_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Delete(const char *pattern, -                              HandlerWithContentReader handler) { -  delete_handlers_for_content_reader_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline Server &Server::Options(const char *pattern, Handler handler) { -  options_handlers_.push_back( -      std::make_pair(std::regex(pattern), std::move(handler))); -  return *this; -} - -inline bool Server::set_base_dir(const char *dir, const char *mount_point) { -  return set_mount_point(mount_point, dir); -} - -inline bool Server::set_mount_point(const char *mount_point, const char *dir, -                                    Headers headers) { -  if (detail::is_dir(dir)) { -    std::string mnt = mount_point ? mount_point : "/"; -    if (!mnt.empty() && mnt[0] == '/') { -      base_dirs_.push_back({mnt, dir, std::move(headers)}); -      return true; -    } -  } -  return false; -} - -inline bool Server::remove_mount_point(const char *mount_point) { -  for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { -    if (it->mount_point == mount_point) { -      base_dirs_.erase(it); -      return true; -    } -  } -  return false; -} - -inline void Server::set_file_extension_and_mimetype_mapping(const char *ext, -                                                            const char *mime) { -  file_extension_and_mimetype_map_[ext] = mime; -} - -inline void Server::set_file_request_handler(Handler handler) { -  file_request_handler_ = std::move(handler); -} - -inline void Server::set_error_handler(Handler handler) { -  error_handler_ = std::move(handler); -} - -inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } - -inline void Server::set_socket_options(SocketOptions socket_options) { -  socket_options_ = std::move(socket_options); -} - -inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); } - -inline void -Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { -  expect_100_continue_handler_ = std::move(handler); -} - -inline void Server::set_keep_alive_max_count(size_t count) { -  keep_alive_max_count_ = count; -} - -inline void Server::set_keep_alive_timeout(time_t sec) { -  keep_alive_timeout_sec_ = sec; -} - -inline void Server::set_read_timeout(time_t sec, time_t usec) { -  read_timeout_sec_ = sec; -  read_timeout_usec_ = usec; -} - -inline void Server::set_write_timeout(time_t sec, time_t usec) { -  write_timeout_sec_ = sec; -  write_timeout_usec_ = usec; -} - -inline void Server::set_idle_interval(time_t sec, time_t usec) { -  idle_interval_sec_ = sec; -  idle_interval_usec_ = usec; -} - -inline void Server::set_payload_max_length(size_t length) { -  payload_max_length_ = length; -} - -inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { -  if (bind_internal(host, port, socket_flags) < 0) return false; -  return true; -} -inline int Server::bind_to_any_port(const char *host, int socket_flags) { -  return bind_internal(host, 0, socket_flags); -} - -inline bool Server::listen_after_bind() { return listen_internal(); } - -inline bool Server::listen(const char *host, int port, int socket_flags) { -  return bind_to_port(host, port, socket_flags) && listen_internal(); -} - -inline bool Server::is_running() const { return is_running_; } - -inline void Server::stop() { -  if (is_running_) { -    assert(svr_sock_ != INVALID_SOCKET); -    std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET)); -    detail::shutdown_socket(sock); -    detail::close_socket(sock); -  } -} - -inline bool Server::parse_request_line(const char *s, Request &req) { -  const static std::regex re( -      "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " -      "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n"); - -  std::cmatch m; -  if (std::regex_match(s, m, re)) { -    req.version = std::string(m[5]); -    req.method = std::string(m[1]); -    req.target = std::string(m[2]); -    req.path = detail::decode_url(m[3], false); - -    // Parse query text -    auto len = std::distance(m[4].first, m[4].second); -    if (len > 0) { detail::parse_query_text(m[4], req.params); } - -    return true; -  } - -  return false; -} - -inline bool Server::write_response(Stream &strm, bool close_connection, -                                   const Request &req, Response &res) { -  assert(res.status != -1); - -  if (400 <= res.status && error_handler_) { error_handler_(req, res); } - -  detail::BufferStream bstrm; - -  // Response line -  if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, -                          detail::status_message(res.status))) { -    return false; -  } - -  // Headers -  if (close_connection || req.get_header_value("Connection") == "close") { -    res.set_header("Connection", "close"); -  } else { -    std::stringstream ss; -    ss << "timeout=" << keep_alive_timeout_sec_ -       << ", max=" << keep_alive_max_count_; -    res.set_header("Keep-Alive", ss.str()); -  } - -  if (!res.has_header("Content-Type") && -      (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { -    res.set_header("Content-Type", "text/plain"); -  } - -  if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { -    res.set_header("Accept-Ranges", "bytes"); -  } - -  std::string content_type; -  std::string boundary; - -  if (req.ranges.size() > 1) { -    boundary = detail::make_multipart_data_boundary(); - -    auto it = res.headers.find("Content-Type"); -    if (it != res.headers.end()) { -      content_type = it->second; -      res.headers.erase(it); -    } - -    res.headers.emplace("Content-Type", -                        "multipart/byteranges; boundary=" + boundary); -  } - -  auto type = detail::encoding_type(req, res); - -  if (res.body.empty()) { -    if (res.content_length_ > 0) { -      size_t length = 0; -      if (req.ranges.empty()) { -        length = res.content_length_; -      } else if (req.ranges.size() == 1) { -        auto offsets = -            detail::get_range_offset_and_length(req, res.content_length_, 0); -        auto offset = offsets.first; -        length = offsets.second; -        auto content_range = detail::make_content_range_header_field( -            offset, length, res.content_length_); -        res.set_header("Content-Range", content_range); -      } else { -        length = detail::get_multipart_ranges_data_length(req, res, boundary, -                                                          content_type); -      } -      res.set_header("Content-Length", std::to_string(length)); -    } else { -      if (res.content_provider_) { -        if (res.is_chunked_content_provider) { -          res.set_header("Transfer-Encoding", "chunked"); -          if (type == detail::EncodingType::Gzip) { -            res.set_header("Content-Encoding", "gzip"); -          } else if (type == detail::EncodingType::Brotli) { -            res.set_header("Content-Encoding", "br"); -          } -        } -      } else { -        res.set_header("Content-Length", "0"); -      } -    } -  } else { -    if (req.ranges.empty()) { -      ; -    } else if (req.ranges.size() == 1) { -      auto offsets = -          detail::get_range_offset_and_length(req, res.body.size(), 0); -      auto offset = offsets.first; -      auto length = offsets.second; -      auto content_range = detail::make_content_range_header_field( -          offset, length, res.body.size()); -      res.set_header("Content-Range", content_range); -      res.body = res.body.substr(offset, length); -    } else { -      res.body = -          detail::make_multipart_ranges_data(req, res, boundary, content_type); -    } - -    if (type != detail::EncodingType::None) { -      std::unique_ptr<detail::compressor> compressor; - -      if (type == detail::EncodingType::Gzip) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -        compressor = detail::make_unique<detail::gzip_compressor>(); -        res.set_header("Content-Encoding", "gzip"); -#endif -      } else if (type == detail::EncodingType::Brotli) { -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -        compressor = detail::make_unique<detail::brotli_compressor>(); -        res.set_header("Content-Encoding", "brotli"); -#endif -      } - -      if (compressor) { -        std::string compressed; - -        if (!compressor->compress(res.body.data(), res.body.size(), true, -                                  [&](const char *data, size_t data_len) { -                                    compressed.append(data, data_len); -                                    return true; -                                  })) { -          return false; -        } - -        res.body.swap(compressed); -      } -    } - -    auto length = std::to_string(res.body.size()); -    res.set_header("Content-Length", length); -  } - -  if (!detail::write_headers(bstrm, res, Headers())) { return false; } - -  // Flush buffer -  auto &data = bstrm.get_buffer(); -  strm.write(data.data(), data.size()); - -  // Body -  auto ret = true; -  if (req.method != "HEAD") { -    if (!res.body.empty()) { -      if (!strm.write(res.body)) { ret = false; } -    } else if (res.content_provider_) { -      if (!write_content_with_provider(strm, req, res, boundary, -                                       content_type)) { -        ret = false; -      } -    } -  } - -  // Log -  if (logger_) { logger_(req, res); } - -  return ret; -} - -inline bool -Server::write_content_with_provider(Stream &strm, const Request &req, -                                    Response &res, const std::string &boundary, -                                    const std::string &content_type) { -  auto is_shutting_down = [this]() { -    return this->svr_sock_ == INVALID_SOCKET; -  }; - -  if (res.content_length_ > 0) { -    if (req.ranges.empty()) { -      if (detail::write_content(strm, res.content_provider_, 0, -                                res.content_length_, is_shutting_down) < 0) { -        return false; -      } -    } else if (req.ranges.size() == 1) { -      auto offsets = -          detail::get_range_offset_and_length(req, res.content_length_, 0); -      auto offset = offsets.first; -      auto length = offsets.second; -      if (detail::write_content(strm, res.content_provider_, offset, length, -                                is_shutting_down) < 0) { -        return false; -      } -    } else { -      if (!detail::write_multipart_ranges_data( -              strm, req, res, boundary, content_type, is_shutting_down)) { -        return false; -      } -    } -  } else { -    if (res.is_chunked_content_provider) { -      auto type = detail::encoding_type(req, res); - -      std::unique_ptr<detail::compressor> compressor; -      if (type == detail::EncodingType::Gzip) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -        compressor = detail::make_unique<detail::gzip_compressor>(); -#endif -      } else if (type == detail::EncodingType::Brotli) { -#ifdef CPPHTTPLIB_BROTLI_SUPPORT -        compressor = detail::make_unique<detail::brotli_compressor>(); -#endif -      } else { -        compressor = detail::make_unique<detail::nocompressor>(); -      } -      assert(compressor != nullptr); - -      if (detail::write_content_chunked(strm, res.content_provider_, -                                        is_shutting_down, *compressor) < 0) { -        return false; -      } -    } else { -      if (detail::write_content_without_length(strm, res.content_provider_, -                                               is_shutting_down) < 0) { -        return false; -      } -    } -  } -  return true; -} - -inline bool Server::read_content(Stream &strm, Request &req, Response &res) { -  MultipartFormDataMap::iterator cur; -  if (read_content_core( -          strm, req, res, -          // Regular -          [&](const char *buf, size_t n) { -            if (req.body.size() + n > req.body.max_size()) { return false; } -            req.body.append(buf, n); -            return true; -          }, -          // Multipart -          [&](const MultipartFormData &file) { -            cur = req.files.emplace(file.name, file); -            return true; -          }, -          [&](const char *buf, size_t n) { -            auto &content = cur->second.content; -            if (content.size() + n > content.max_size()) { return false; } -            content.append(buf, n); -            return true; -          })) { -    const auto &content_type = req.get_header_value("Content-Type"); -    if (!content_type.find("application/x-www-form-urlencoded")) { -      detail::parse_query_text(req.body, req.params); -    } -    return true; -  } -  return false; -} - -inline bool Server::read_content_with_content_receiver( -    Stream &strm, Request &req, Response &res, ContentReceiver receiver, -    MultipartContentHeader multipart_header, -    ContentReceiver multipart_receiver) { -  return read_content_core(strm, req, res, std::move(receiver), -                           std::move(multipart_header), -                           std::move(multipart_receiver)); -} - -inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, -                                      ContentReceiver receiver, -                                      MultipartContentHeader mulitpart_header, -                                      ContentReceiver multipart_receiver) { -  detail::MultipartFormDataParser multipart_form_data_parser; -  ContentReceiverWithProgress out; - -  if (req.is_multipart_form_data()) { -    const auto &content_type = req.get_header_value("Content-Type"); -    std::string boundary; -    if (!detail::parse_multipart_boundary(content_type, boundary)) { -      res.status = 400; -      return false; -    } - -    multipart_form_data_parser.set_boundary(std::move(boundary)); -    out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { -      /* For debug -      size_t pos = 0; -      while (pos < n) { -        auto read_size = std::min<size_t>(1, n - pos); -        auto ret = multipart_form_data_parser.parse( -            buf + pos, read_size, multipart_receiver, mulitpart_header); -        if (!ret) { return false; } -        pos += read_size; -      } -      return true; -      */ -      return multipart_form_data_parser.parse(buf, n, multipart_receiver, -                                              mulitpart_header); -    }; -  } else { -    out = [receiver](const char *buf, size_t n, uint64_t /*off*/, -                     uint64_t /*len*/) { return receiver(buf, n); }; -  } - -  if (req.method == "DELETE" && !req.has_header("Content-Length")) { -    return true; -  } - -  if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, -                            out, true)) { -    return false; -  } - -  if (req.is_multipart_form_data()) { -    if (!multipart_form_data_parser.is_valid()) { -      res.status = 400; -      return false; -    } -  } - -  return true; -} - -inline bool Server::handle_file_request(Request &req, Response &res, -                                        bool head) { -  for (const auto &entry : base_dirs_) { -    // Prefix match -    if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { -      std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); -      if (detail::is_valid_path(sub_path)) { -        auto path = entry.base_dir + sub_path; -        if (path.back() == '/') { path += "index.html"; } - -        if (detail::is_file(path)) { -          detail::read_file(path, res.body); -          auto type = -              detail::find_content_type(path, file_extension_and_mimetype_map_); -          if (type) { res.set_header("Content-Type", type); } -          for (const auto &kv : entry.headers) { -            res.set_header(kv.first.c_str(), kv.second); -          } -          res.status = 200; -          if (!head && file_request_handler_) { -            file_request_handler_(req, res); -          } -          return true; -        } -      } -    } -  } -  return false; -} - -inline socket_t -Server::create_server_socket(const char *host, int port, int socket_flags, -                             SocketOptions socket_options) const { -  return detail::create_socket( -      host, port, socket_flags, tcp_nodelay_, std::move(socket_options), -      [](socket_t sock, struct addrinfo &ai) -> bool { -        if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) { -          return false; -        } -        if (::listen(sock, 5)) { // Listen through 5 channels -          return false; -        } -        return true; -      }); -} - -inline int Server::bind_internal(const char *host, int port, int socket_flags) { -  if (!is_valid()) { return -1; } - -  svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); -  if (svr_sock_ == INVALID_SOCKET) { return -1; } - -  if (port == 0) { -    struct sockaddr_storage addr; -    socklen_t addr_len = sizeof(addr); -    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr), -                    &addr_len) == -1) { -      return -1; -    } -    if (addr.ss_family == AF_INET) { -      return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port); -    } else if (addr.ss_family == AF_INET6) { -      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port); -    } else { -      return -1; -    } -  } else { -    return port; -  } -} - -inline bool Server::listen_internal() { -  auto ret = true; -  is_running_ = true; - -  { -    std::unique_ptr<TaskQueue> task_queue(new_task_queue()); - -    while (svr_sock_ != INVALID_SOCKET) { -#ifndef _WIN32 -      if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { -#endif -        auto val = detail::select_read(svr_sock_, idle_interval_sec_, -                                       idle_interval_usec_); -        if (val == 0) { // Timeout -          task_queue->on_idle(); -          continue; -        } -#ifndef _WIN32 -      } -#endif -      socket_t sock = accept(svr_sock_, nullptr, nullptr); - -      if (sock == INVALID_SOCKET) { -        if (errno == EMFILE) { -          // The per-process limit of open file descriptors has been reached. -          // Try to accept new connections after a short sleep. -          std::this_thread::sleep_for(std::chrono::milliseconds(1)); -          continue; -        } -        if (svr_sock_ != INVALID_SOCKET) { -          detail::close_socket(svr_sock_); -          ret = false; -        } else { -          ; // The server socket was closed by user. -        } -        break; -      } - -#if __cplusplus > 201703L -      task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); -#else -      task_queue->enqueue([=]() { process_and_close_socket(sock); }); -#endif -    } - -    task_queue->shutdown(); -  } - -  is_running_ = false; -  return ret; -} - -inline bool Server::routing(Request &req, Response &res, Stream &strm) { -  // File handler -  bool is_head_request = req.method == "HEAD"; -  if ((req.method == "GET" || is_head_request) && -      handle_file_request(req, res, is_head_request)) { -    return true; -  } - -  if (detail::expect_content(req)) { -    // Content reader handler -    { -      ContentReader reader( -          [&](ContentReceiver receiver) { -            return read_content_with_content_receiver( -                strm, req, res, std::move(receiver), nullptr, nullptr); -          }, -          [&](MultipartContentHeader header, ContentReceiver receiver) { -            return read_content_with_content_receiver(strm, req, res, nullptr, -                                                      std::move(header), -                                                      std::move(receiver)); -          }); - -      if (req.method == "POST") { -        if (dispatch_request_for_content_reader( -                req, res, std::move(reader), -                post_handlers_for_content_reader_)) { -          return true; -        } -      } else if (req.method == "PUT") { -        if (dispatch_request_for_content_reader( -                req, res, std::move(reader), -                put_handlers_for_content_reader_)) { -          return true; -        } -      } else if (req.method == "PATCH") { -        if (dispatch_request_for_content_reader( -                req, res, std::move(reader), -                patch_handlers_for_content_reader_)) { -          return true; -        } -      } else if (req.method == "DELETE") { -        if (dispatch_request_for_content_reader( -                req, res, std::move(reader), -                delete_handlers_for_content_reader_)) { -          return true; -        } -      } -    } - -    // Read content into `req.body` -    if (!read_content(strm, req, res)) { return false; } -  } - -  // Regular handler -  if (req.method == "GET" || req.method == "HEAD") { -    return dispatch_request(req, res, get_handlers_); -  } else if (req.method == "POST") { -    return dispatch_request(req, res, post_handlers_); -  } else if (req.method == "PUT") { -    return dispatch_request(req, res, put_handlers_); -  } else if (req.method == "DELETE") { -    return dispatch_request(req, res, delete_handlers_); -  } else if (req.method == "OPTIONS") { -    return dispatch_request(req, res, options_handlers_); -  } else if (req.method == "PATCH") { -    return dispatch_request(req, res, patch_handlers_); -  } - -  res.status = 400; -  return false; -} - -inline bool Server::dispatch_request(Request &req, Response &res, -                                     const Handlers &handlers) { -  try { -    for (const auto &x : handlers) { -      const auto &pattern = x.first; -      const auto &handler = x.second; - -      if (std::regex_match(req.path, req.matches, pattern)) { -        handler(req, res); -        return true; -      } -    } -  } catch (const std::exception &ex) { -    res.status = 500; -    res.set_header("EXCEPTION_WHAT", ex.what()); -  } catch (...) { -    res.status = 500; -    res.set_header("EXCEPTION_WHAT", "UNKNOWN"); -  } -  return false; -} - -inline bool Server::dispatch_request_for_content_reader( -    Request &req, Response &res, ContentReader content_reader, -    const HandlersForContentReader &handlers) { -  for (const auto &x : handlers) { -    const auto &pattern = x.first; -    const auto &handler = x.second; - -    if (std::regex_match(req.path, req.matches, pattern)) { -      handler(req, res, content_reader); -      return true; -    } -  } -  return false; -} - -inline bool -Server::process_request(Stream &strm, bool close_connection, -                        bool &connection_closed, -                        const std::function<void(Request &)> &setup_request) { -  std::array<char, 2048> buf{}; - -  detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); - -  // Connection has been closed on client -  if (!line_reader.getline()) { return false; } - -  Request req; -  Response res; - -  res.version = "HTTP/1.1"; - -  // Check if the request URI doesn't exceed the limit -  if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { -    Headers dummy; -    detail::read_headers(strm, dummy); -    res.status = 414; -    return write_response(strm, close_connection, req, res); -  } - -  // Request line and headers -  if (!parse_request_line(line_reader.ptr(), req) || -      !detail::read_headers(strm, req.headers)) { -    res.status = 400; -    return write_response(strm, close_connection, req, res); -  } - -  if (req.get_header_value("Connection") == "close") { -    connection_closed = true; -  } - -  if (req.version == "HTTP/1.0" && -      req.get_header_value("Connection") != "Keep-Alive") { -    connection_closed = true; -  } - -  strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); -  req.set_header("REMOTE_ADDR", req.remote_addr); -  req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); - -  if (req.has_header("Range")) { -    const auto &range_header_value = req.get_header_value("Range"); -    if (!detail::parse_range_header(range_header_value, req.ranges)) { -      res.status = 416; -      return write_response(strm, close_connection, req, res); -    } -  } - -  if (setup_request) { setup_request(req); } - -  if (req.get_header_value("Expect") == "100-continue") { -    auto status = 100; -    if (expect_100_continue_handler_) { -      status = expect_100_continue_handler_(req, res); -    } -    switch (status) { -    case 100: -    case 417: -      strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, -                        detail::status_message(status)); -      break; -    default: return write_response(strm, close_connection, req, res); -    } -  } - -  // Rounting -  if (routing(req, res, strm)) { -    if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } -  } else { -    if (res.status == -1) { res.status = 404; } -  } - -  return write_response(strm, close_connection, req, res); -} - -inline bool Server::is_valid() const { return true; } - -inline bool Server::process_and_close_socket(socket_t sock) { -  auto ret = detail::process_server_socket( -      sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, -      read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, -      [this](Stream &strm, bool close_connection, bool &connection_closed) { -        return process_request(strm, close_connection, connection_closed, -                               nullptr); -      }); - -  detail::shutdown_socket(sock); -  detail::close_socket(sock); -  return ret; -} - -// HTTP client implementation -inline ClientImpl::ClientImpl(const std::string &host) -    : ClientImpl(host, 80, std::string(), std::string()) {} - -inline ClientImpl::ClientImpl(const std::string &host, int port) -    : ClientImpl(host, port, std::string(), std::string()) {} - -inline ClientImpl::ClientImpl(const std::string &host, int port, -                              const std::string &client_cert_path, -                              const std::string &client_key_path) -    : error_(Error::Success), host_(host), port_(port), -      host_and_port_(host_ + ":" + std::to_string(port_)), -      client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} - -inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); } - -inline bool ClientImpl::is_valid() const { return true; } - -inline Error ClientImpl::get_last_error() const { return error_; } - -inline void ClientImpl::copy_settings(const ClientImpl &rhs) { -  client_cert_path_ = rhs.client_cert_path_; -  client_key_path_ = rhs.client_key_path_; -  connection_timeout_sec_ = rhs.connection_timeout_sec_; -  read_timeout_sec_ = rhs.read_timeout_sec_; -  read_timeout_usec_ = rhs.read_timeout_usec_; -  write_timeout_sec_ = rhs.write_timeout_sec_; -  write_timeout_usec_ = rhs.write_timeout_usec_; -  basic_auth_username_ = rhs.basic_auth_username_; -  basic_auth_password_ = rhs.basic_auth_password_; -  bearer_token_auth_token_ = rhs.bearer_token_auth_token_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  digest_auth_username_ = rhs.digest_auth_username_; -  digest_auth_password_ = rhs.digest_auth_password_; -#endif -  keep_alive_ = rhs.keep_alive_; -  follow_location_ = rhs.follow_location_; -  tcp_nodelay_ = rhs.tcp_nodelay_; -  socket_options_ = rhs.socket_options_; -  compress_ = rhs.compress_; -  decompress_ = rhs.decompress_; -  interface_ = rhs.interface_; -  proxy_host_ = rhs.proxy_host_; -  proxy_port_ = rhs.proxy_port_; -  proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; -  proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; -  proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; -  proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; -#endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  server_certificate_verification_ = rhs.server_certificate_verification_; -#endif -  logger_ = rhs.logger_; -} - -inline socket_t ClientImpl::create_client_socket() const { -  if (!proxy_host_.empty() && proxy_port_ != -1) { -    return detail::create_client_socket( -        proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, -        connection_timeout_sec_, connection_timeout_usec_, interface_, error_); -  } -  return detail::create_client_socket( -      host_.c_str(), port_, tcp_nodelay_, socket_options_, -      connection_timeout_sec_, connection_timeout_usec_, interface_, error_); -} - -inline bool ClientImpl::create_and_connect_socket(Socket &socket) { -  auto sock = create_client_socket(); -  if (sock == INVALID_SOCKET) { return false; } -  socket.sock = sock; -  return true; -} - -inline void ClientImpl::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { -  (void)socket; -  (void)shutdown_gracefully; -  //If there are any requests in flight from threads other than us, then it's -  //a thread-unsafe race because individual ssl* objects are not thread-safe.  -  assert(socket_requests_in_flight_ == 0 || -         socket_requests_are_from_thread_ == std::this_thread::get_id()); -} - -inline void ClientImpl::shutdown_socket(Socket &socket) { -  if (socket.sock == INVALID_SOCKET) -    return;   -  detail::shutdown_socket(socket.sock); -} -  -inline void ClientImpl::close_socket(Socket &socket) { -  // If there are requests in flight in another thread, usually closing -  // the socket will be fine and they will simply receive an error when -  // using the closed socket, but it is still a bug since rarely the OS -  // may reassign the socket id to be used for a new socket, and then -  // suddenly they will be operating on a live socket that is different -  // than the one they intended! -  assert(socket_requests_in_flight_ == 0 || -         socket_requests_are_from_thread_ == std::this_thread::get_id()); -  // It is also a bug if this happens while SSL is still active -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  assert(socket.ssl == nullptr); -#endif -  if (socket.sock == INVALID_SOCKET) -    return; -  detail::close_socket(socket.sock); -  socket.sock = INVALID_SOCKET; -} - -inline void ClientImpl::lock_socket_and_shutdown_and_close() { -  std::lock_guard<std::mutex> guard(socket_mutex_); -  shutdown_ssl(socket_, true); -  shutdown_socket(socket_); -  close_socket(socket_); -} -  -inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { -  std::array<char, 2048> buf; - -  detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); - -  if (!line_reader.getline()) { return false; } - -  const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n"); - -  std::cmatch m; -  if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } -  res.version = std::string(m[1]); -  res.status = std::stoi(std::string(m[2])); -  res.reason = std::string(m[3]); - -  // Ignore '100 Continue' -  while (res.status == 100) { -    if (!line_reader.getline()) { return false; } // CRLF -    if (!line_reader.getline()) { return false; } // next response line - -    if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } -    res.version = std::string(m[1]); -    res.status = std::stoi(std::string(m[2])); -    res.reason = std::string(m[3]); -  } - -  return true; -} - -inline bool ClientImpl::send(const Request &req, Response &res) { -  std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_); - -  { -    std::lock_guard<std::mutex> guard(socket_mutex_); -    // Set this to false immediately - if it ever gets set to true by the end of the -    // request, we know another thread instructed us to close the socket. -    socket_should_be_closed_when_request_is_done_ = false; - -    auto is_alive = false; -    if (socket_.is_open()) { -      is_alive = detail::select_write(socket_.sock, 0, 0) > 0; -      if (!is_alive) { -        // Attempt to avoid sigpipe by shutting down nongracefully if it seems like -        // the other side has already closed the connection -        // Also, there cannot be any requests in flight from other threads since we locked -        // request_mutex_, so safe to close everything immediately -        const bool shutdown_gracefully = false; -        shutdown_ssl(socket_, shutdown_gracefully); -        shutdown_socket(socket_); -        close_socket(socket_); -      } -    } - -    if (!is_alive) { -      if (!create_and_connect_socket(socket_)) { return false; } - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -      // TODO: refactoring -      if (is_ssl()) { -        auto &scli = static_cast<SSLClient &>(*this); -        if (!proxy_host_.empty() && proxy_port_ != -1) { -          bool success = false; -          if (!scli.connect_with_proxy(socket_, res, success)) { -            return success; -          } -        } - -        if (!scli.initialize_ssl(socket_)) { return false; } -      } -#endif -    } - -    // Mark the current socket as being in use so that it cannot be closed by anyone -    // else while this request is ongoing, even though we will be releasing the mutex. -    if (socket_requests_in_flight_ > 1) { -      assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); -    } -    socket_requests_in_flight_ += 1; -    socket_requests_are_from_thread_ = std::this_thread::get_id(); -  } - -  auto close_connection = !keep_alive_; -  auto ret = process_socket(socket_, [&](Stream &strm) { -    return handle_request(strm, req, res, close_connection); -  }); - -  //Briefly lock mutex in order to mark that a request is no longer ongoing -  { -    std::lock_guard<std::mutex> guard(socket_mutex_); -    socket_requests_in_flight_ -= 1; -    if (socket_requests_in_flight_ <= 0) { -      assert(socket_requests_in_flight_ == 0); -      socket_requests_are_from_thread_ = std::thread::id(); -    } - -    if (socket_should_be_closed_when_request_is_done_ || -        close_connection || -        !ret ) { -      shutdown_ssl(socket_, true); -      shutdown_socket(socket_); -      close_socket(socket_); -    } -  } - -  if (!ret) { -    if (error_ == Error::Success) { error_ = Error::Unknown; } -  } - -  return ret; -} - -inline bool ClientImpl::handle_request(Stream &strm, const Request &req, -                                       Response &res, bool close_connection) { -  if (req.path.empty()) { -    error_ = Error::Connection; -    return false; -  } - -  bool ret; - -  if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { -    auto req2 = req; -    req2.path = "http://" + host_and_port_ + req.path; -    ret = process_request(strm, req2, res, close_connection); -  } else { -    ret = process_request(strm, req, res, close_connection); -  } - -  if (!ret) { return false; } - -  if (300 < res.status && res.status < 400 && follow_location_) { -    ret = redirect(req, res); -  } - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -  if ((res.status == 401 || res.status == 407) && -      req.authorization_count_ < 5) { -    auto is_proxy = res.status == 407; -    const auto &username = -        is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; -    const auto &password = -        is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; - -    if (!username.empty() && !password.empty()) { -      std::map<std::string, std::string> auth; -      if (detail::parse_www_authenticate(res, auth, is_proxy)) { -        Request new_req = req; -        new_req.authorization_count_ += 1; -        auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; -        new_req.headers.erase(key); -        new_req.headers.insert(detail::make_digest_authentication_header( -            req, auth, new_req.authorization_count_, detail::random_string(10), -            username, password, is_proxy)); - -        Response new_res; - -        ret = send(new_req, new_res); -        if (ret) { res = new_res; } -      } -    } -  } -#endif - -  return ret; -} - -inline bool ClientImpl::redirect(const Request &req, Response &res) { -  if (req.redirect_count == 0) { -    error_ = Error::ExceedRedirectCount; -    return false; -  } - -  auto location = detail::decode_url(res.get_header_value("location"), true); -  if (location.empty()) { return false; } - -  const static std::regex re( -      R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); - -  std::smatch m; -  if (!std::regex_match(location, m, re)) { return false; } - -  auto scheme = is_ssl() ? "https" : "http"; - -  auto next_scheme = m[1].str(); -  auto next_host = m[2].str(); -  auto port_str = m[3].str(); -  auto next_path = m[4].str(); - -  auto next_port = port_; -  if (!port_str.empty()) { -    next_port = std::stoi(port_str); -  } else if (!next_scheme.empty()) { -    next_port = next_scheme == "https" ? 443 : 80; -  } - -  if (next_scheme.empty()) { next_scheme = scheme; } -  if (next_host.empty()) { next_host = host_; } -  if (next_path.empty()) { next_path = "/"; } - -  if (next_scheme == scheme && next_host == host_ && next_port == port_) { -    return detail::redirect(*this, req, res, next_path); -  } else { -    if (next_scheme == "https") { -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -      SSLClient cli(next_host.c_str(), next_port); -      cli.copy_settings(*this); -      auto ret = detail::redirect(cli, req, res, next_path); -      if (!ret) { error_ = cli.get_last_error(); } -      return ret; -#else -      return false; -#endif -    } else { -      ClientImpl cli(next_host.c_str(), next_port); -      cli.copy_settings(*this); -      auto ret = detail::redirect(cli, req, res, next_path); -      if (!ret) { error_ = cli.get_last_error(); } -      return ret; -    } -  } -} - -inline bool ClientImpl::write_request(Stream &strm, const Request &req, -                                      bool close_connection) { -  detail::BufferStream bstrm; - -  // Request line -  const auto &path = detail::encode_url(req.path); - -  bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); - -  // Additonal headers -  Headers headers; -  if (close_connection) { headers.emplace("Connection", "close"); } - -  if (!req.has_header("Host")) { -    if (is_ssl()) { -      if (port_ == 443) { -        headers.emplace("Host", host_); -      } else { -        headers.emplace("Host", host_and_port_); -      } -    } else { -      if (port_ == 80) { -        headers.emplace("Host", host_); -      } else { -        headers.emplace("Host", host_and_port_); -      } -    } -  } - -  if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } - -  if (!req.has_header("User-Agent")) { -    headers.emplace("User-Agent", "cpp-httplib/0.7"); -  } - -  if (req.body.empty()) { -    if (req.content_provider) { -      auto length = std::to_string(req.content_length); -      headers.emplace("Content-Length", length); -    } else { -      if (req.method == "POST" || req.method == "PUT" || -          req.method == "PATCH") { -        headers.emplace("Content-Length", "0"); -      } -    } -  } else { -    if (!req.has_header("Content-Type")) { -      headers.emplace("Content-Type", "text/plain"); -    } - -    if (!req.has_header("Content-Length")) { -      auto length = std::to_string(req.body.size()); -      headers.emplace("Content-Length", length); -    } -  } - -  if (!basic_auth_password_.empty()) { -    headers.insert(make_basic_authentication_header( -        basic_auth_username_, basic_auth_password_, false)); -  } - -  if (!proxy_basic_auth_username_.empty() && -      !proxy_basic_auth_password_.empty()) { -    headers.insert(make_basic_authentication_header( -        proxy_basic_auth_username_, proxy_basic_auth_password_, true)); -  } - -  if (!bearer_token_auth_token_.empty()) { -    headers.insert(make_bearer_token_authentication_header( -        bearer_token_auth_token_, false)); -  } - -  if (!proxy_bearer_token_auth_token_.empty()) { -    headers.insert(make_bearer_token_authentication_header( -        proxy_bearer_token_auth_token_, true)); -  } - -  detail::write_headers(bstrm, req, headers); - -  // Flush buffer -  auto &data = bstrm.get_buffer(); -  if (!detail::write_data(strm, data.data(), data.size())) { -    error_ = Error::Write; -    return false; -  } - -  // Body -  if (req.body.empty()) { -    if (req.content_provider) { -      size_t offset = 0; -      size_t end_offset = req.content_length; - -      bool ok = true; - -      DataSink data_sink; -      data_sink.write = [&](const char *d, size_t l) { -        if (ok) { -          if (detail::write_data(strm, d, l)) { -            offset += l; -          } else { -            ok = false; -          } -        } -      }; -      data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - -      while (offset < end_offset) { -        if (!req.content_provider(offset, end_offset - offset, data_sink)) { -          error_ = Error::Canceled; -          return false; -        } -        if (!ok) { -          error_ = Error::Write; -          return false; -        } -      } -    } -  } else { -    return detail::write_data(strm, req.body.data(), req.body.size()); -  } - -  return true; -} - -inline std::unique_ptr<Response> ClientImpl::send_with_content_provider( -    const char *method, const char *path, const Headers &headers, -    const std::string &body, size_t content_length, -    ContentProvider content_provider, const char *content_type) { - -  Request req; -  req.method = method; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.path = path; - -  if (content_type) { req.headers.emplace("Content-Type", content_type); } - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -  if (compress_) { -    detail::gzip_compressor compressor; - -    if (content_provider) { -      auto ok = true; -      size_t offset = 0; - -      DataSink data_sink; -      data_sink.write = [&](const char *data, size_t data_len) { -        if (ok) { -          auto last = offset + data_len == content_length; - -          auto ret = compressor.compress( -              data, data_len, last, [&](const char *data, size_t data_len) { -                req.body.append(data, data_len); -                return true; -              }); - -          if (ret) { -            offset += data_len; -          } else { -            ok = false; -          } -        } -      }; -      data_sink.is_writable = [&](void) { return ok && true; }; - -      while (ok && offset < content_length) { -        if (!content_provider(offset, content_length - offset, data_sink)) { -          error_ = Error::Canceled; -          return nullptr; -        } -      } -    } else { -      if (!compressor.compress(body.data(), body.size(), true, -                               [&](const char *data, size_t data_len) { -                                 req.body.append(data, data_len); -                                 return true; -                               })) { -        return nullptr; -      } -    } - -    req.headers.emplace("Content-Encoding", "gzip"); -  } else -#endif -  { -    if (content_provider) { -      req.content_length = content_length; -      req.content_provider = std::move(content_provider); -    } else { -      req.body = body; -    } -  } - -  auto res = detail::make_unique<Response>(); - -  return send(req, *res) ? std::move(res) : nullptr; -} - -inline bool ClientImpl::process_request(Stream &strm, const Request &req, -                                        Response &res, bool close_connection) { -  // Send request -  if (!write_request(strm, req, close_connection)) { return false; } - -  // Receive response and headers -  if (!read_response_line(strm, res) || -      !detail::read_headers(strm, res.headers)) { -    error_ = Error::Read; -    return false; -  } - -  if (req.response_handler) { -    if (!req.response_handler(res)) { -      error_ = Error::Canceled; -      return false; -    } -  } - -  // Body -  if (req.method != "HEAD" && req.method != "CONNECT") { -    auto out = -        req.content_receiver -            ? static_cast<ContentReceiverWithProgress>( -                  [&](const char *buf, size_t n, uint64_t off, uint64_t len) { -                    auto ret = req.content_receiver(buf, n, off, len); -                    if (!ret) { error_ = Error::Canceled; } -                    return ret; -                  }) -            : static_cast<ContentReceiverWithProgress>( -                  [&](const char *buf, size_t n, uint64_t /*off*/, -                      uint64_t /*len*/) { -                    if (res.body.size() + n > res.body.max_size()) { -                      return false; -                    } -                    res.body.append(buf, n); -                    return true; -                  }); - -    auto progress = [&](uint64_t current, uint64_t total) { -      if (!req.progress) { return true; } -      auto ret = req.progress(current, total); -      if (!ret) { error_ = Error::Canceled; } -      return ret; -    }; - -    int dummy_status; -    if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(), -                              dummy_status, std::move(progress), std::move(out), -                              decompress_)) { -      if (error_ != Error::Canceled) { error_ = Error::Read; } -      return false; -    } -  } - -  if (res.get_header_value("Connection") == "close" || -      (res.version == "HTTP/1.0" && res.reason != "Connection established")) { -    // TODO this requires a not-entirely-obvious chain of calls to be correct -    // for this to be safe. Maybe a code refactor (such as moving this out to -    // the send function and getting rid of the recursiveness of the mutex) -    // could make this more obvious. -     -    // This is safe to call because process_request is only called by handle_request -    // which is only called by send, which locks the request mutex during the process. -    // It would be a bug to call it from a different thread since it's a thread-safety -    // issue to do these things to the socket if another thread is using the socket. -    lock_socket_and_shutdown_and_close(); -  } - -  // Log -  if (logger_) { logger_(req, res); } - -  return true; -} - -inline bool -ClientImpl::process_socket(const Socket &socket, -                           std::function<bool(Stream &strm)> callback) { -  return detail::process_client_socket( -      socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, -      write_timeout_usec_, std::move(callback)); -} - -inline bool ClientImpl::is_ssl() const { return false; } - -inline Result ClientImpl::Get(const char *path) { -  return Get(path, Headers(), Progress()); -} - -inline Result ClientImpl::Get(const char *path, Progress progress) { -  return Get(path, Headers(), std::move(progress)); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers) { -  return Get(path, headers, Progress()); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers, -                              Progress progress) { -  Request req; -  req.method = "GET"; -  req.path = path; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.progress = std::move(progress); - -  auto res = detail::make_unique<Response>(); -  auto ret = send(req, *res); -  return Result{ret ? std::move(res) : nullptr, get_last_error()}; -} - -inline Result ClientImpl::Get(const char *path, -                              ContentReceiver content_receiver) { -  return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const char *path, -                              ContentReceiver content_receiver, -                              Progress progress) { -  return Get(path, Headers(), nullptr, std::move(content_receiver), -             std::move(progress)); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers, -                              ContentReceiver content_receiver) { -  return Get(path, headers, nullptr, std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers, -                              ContentReceiver content_receiver, -                              Progress progress) { -  return Get(path, headers, nullptr, std::move(content_receiver), -             std::move(progress)); -} - -inline Result ClientImpl::Get(const char *path, -                              ResponseHandler response_handler, -                              ContentReceiver content_receiver) { -  return Get(path, Headers(), std::move(response_handler), -             std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers, -                              ResponseHandler response_handler, -                              ContentReceiver content_receiver) { -  return Get(path, headers, std::move(response_handler), -             std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const char *path, -                              ResponseHandler response_handler, -                              ContentReceiver content_receiver, -                              Progress progress) { -  return Get(path, Headers(), std::move(response_handler), -             std::move(content_receiver), std::move(progress)); -} - -inline Result ClientImpl::Get(const char *path, const Headers &headers, -                              ResponseHandler response_handler, -                              ContentReceiver content_receiver, -                              Progress progress) { -  Request req; -  req.method = "GET"; -  req.path = path; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.response_handler = std::move(response_handler); -  req.content_receiver = -      [content_receiver](const char *data, size_t data_length, -                         uint64_t /*offset*/, uint64_t /*total_length*/) { -        return content_receiver(data, data_length); -      }; -  req.progress = std::move(progress); - -  auto res = detail::make_unique<Response>(); -  auto ret = send(req, *res); -  return Result{ret ? std::move(res) : nullptr, get_last_error()}; -} - -inline Result ClientImpl::Head(const char *path) { -  return Head(path, Headers()); -} - -inline Result ClientImpl::Head(const char *path, const Headers &headers) { -  Request req; -  req.method = "HEAD"; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.path = path; - -  auto res = detail::make_unique<Response>(); -  auto ret = send(req, *res); -  return Result{ret ? std::move(res) : nullptr, get_last_error()}; -} - -inline Result ClientImpl::Post(const char *path) { -  return Post(path, std::string(), nullptr); -} - -inline Result ClientImpl::Post(const char *path, const std::string &body, -                               const char *content_type) { -  return Post(path, Headers(), body, content_type); -} - -inline Result ClientImpl::Post(const char *path, const Headers &headers, -                               const std::string &body, -                               const char *content_type) { -  auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr, -                                        content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Post(const char *path, const Params ¶ms) { -  return Post(path, Headers(), params); -} - -inline Result ClientImpl::Post(const char *path, size_t content_length, -                               ContentProvider content_provider, -                               const char *content_type) { -  return Post(path, Headers(), content_length, std::move(content_provider), -              content_type); -} - -inline Result ClientImpl::Post(const char *path, const Headers &headers, -                               size_t content_length, -                               ContentProvider content_provider, -                               const char *content_type) { -  auto ret = send_with_content_provider( -      "POST", path, headers, std::string(), content_length, -      std::move(content_provider), content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Post(const char *path, const Headers &headers, -                               const Params ¶ms) { -  auto query = detail::params_to_query_str(params); -  return Post(path, headers, query, "application/x-www-form-urlencoded"); -} - -inline Result ClientImpl::Post(const char *path, -                               const MultipartFormDataItems &items) { -  return Post(path, Headers(), items); -} - -inline Result ClientImpl::Post(const char *path, const Headers &headers, -                               const MultipartFormDataItems &items) { -  return Post(path, headers, items, detail::make_multipart_data_boundary()); -} -inline Result ClientImpl::Post(const char *path, const Headers &headers, -                               const MultipartFormDataItems &items, -                               const std::string &boundary) { -  for (size_t i = 0; i < boundary.size(); i++) { -    char c = boundary[i]; -    if (!std::isalnum(c) && c != '-' && c != '_') { -      error_ = Error::UnsupportedMultipartBoundaryChars; -      return Result{nullptr, error_}; -    } -  } - -  std::string body; - -  for (const auto &item : items) { -    body += "--" + boundary + "\r\n"; -    body += "Content-Disposition: form-data; name=\"" + item.name + "\""; -    if (!item.filename.empty()) { -      body += "; filename=\"" + item.filename + "\""; -    } -    body += "\r\n"; -    if (!item.content_type.empty()) { -      body += "Content-Type: " + item.content_type + "\r\n"; -    } -    body += "\r\n"; -    body += item.content + "\r\n"; -  } - -  body += "--" + boundary + "--\r\n"; - -  std::string content_type = "multipart/form-data; boundary=" + boundary; -  return Post(path, headers, body, content_type.c_str()); -} - -inline Result ClientImpl::Put(const char *path) { -  return Put(path, std::string(), nullptr); -} - -inline Result ClientImpl::Put(const char *path, const std::string &body, -                              const char *content_type) { -  return Put(path, Headers(), body, content_type); -} - -inline Result ClientImpl::Put(const char *path, const Headers &headers, -                              const std::string &body, -                              const char *content_type) { -  auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr, -                                        content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Put(const char *path, size_t content_length, -                              ContentProvider content_provider, -                              const char *content_type) { -  return Put(path, Headers(), content_length, std::move(content_provider), -             content_type); -} - -inline Result ClientImpl::Put(const char *path, const Headers &headers, -                              size_t content_length, -                              ContentProvider content_provider, -                              const char *content_type) { -  auto ret = send_with_content_provider( -      "PUT", path, headers, std::string(), content_length, -      std::move(content_provider), content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Put(const char *path, const Params ¶ms) { -  return Put(path, Headers(), params); -} - -inline Result ClientImpl::Put(const char *path, const Headers &headers, -                              const Params ¶ms) { -  auto query = detail::params_to_query_str(params); -  return Put(path, headers, query, "application/x-www-form-urlencoded"); -} - -inline Result ClientImpl::Patch(const char *path, const std::string &body, -                                const char *content_type) { -  return Patch(path, Headers(), body, content_type); -} - -inline Result ClientImpl::Patch(const char *path, const Headers &headers, -                                const std::string &body, -                                const char *content_type) { -  auto ret = send_with_content_provider("PATCH", path, headers, body, 0, -                                        nullptr, content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Patch(const char *path, size_t content_length, -                                ContentProvider content_provider, -                                const char *content_type) { -  return Patch(path, Headers(), content_length, std::move(content_provider), -               content_type); -} - -inline Result ClientImpl::Patch(const char *path, const Headers &headers, -                                size_t content_length, -                                ContentProvider content_provider, -                                const char *content_type) { -  auto ret = send_with_content_provider( -      "PATCH", path, headers, std::string(), content_length, -      std::move(content_provider), content_type); -  return Result{std::move(ret), get_last_error()}; -} - -inline Result ClientImpl::Delete(const char *path) { -  return Delete(path, Headers(), std::string(), nullptr); -} - -inline Result ClientImpl::Delete(const char *path, const std::string &body, -                                 const char *content_type) { -  return Delete(path, Headers(), body, content_type); -} - -inline Result ClientImpl::Delete(const char *path, const Headers &headers) { -  return Delete(path, headers, std::string(), nullptr); -} - -inline Result ClientImpl::Delete(const char *path, const Headers &headers, -                                 const std::string &body, -                                 const char *content_type) { -  Request req; -  req.method = "DELETE"; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.path = path; - -  if (content_type) { req.headers.emplace("Content-Type", content_type); } -  req.body = body; - -  auto res = detail::make_unique<Response>(); -  auto ret = send(req, *res); -  return Result{ret ? std::move(res) : nullptr, get_last_error()}; -} - -inline Result ClientImpl::Options(const char *path) { -  return Options(path, Headers()); -} - -inline Result ClientImpl::Options(const char *path, const Headers &headers) { -  Request req; -  req.method = "OPTIONS"; -  req.headers = default_headers_; -  req.headers.insert(headers.begin(), headers.end()); -  req.path = path; - -  auto res = detail::make_unique<Response>(); -  auto ret = send(req, *res); -  return Result{ret ? std::move(res) : nullptr, get_last_error()}; -} - -inline size_t ClientImpl::is_socket_open() const { -  std::lock_guard<std::mutex> guard(socket_mutex_); -  return socket_.is_open(); -} - -inline void ClientImpl::stop() { -  std::lock_guard<std::mutex> guard(socket_mutex_); -  // There is no guarantee that this doesn't get overwritten later, but set it so that -  // there is a good chance that any threads stopping as a result pick up this error. -  error_ = Error::Canceled; -   -  // If there is anything ongoing right now, the ONLY thread-safe thing we can do -  // is to shutdown_socket, so that threads using this socket suddenly discover -  // they can't read/write any more and error out. -  // Everything else (closing the socket, shutting ssl down) is unsafe because these -  // actions are not thread-safe. -  if (socket_requests_in_flight_ > 0) { -    shutdown_socket(socket_); -    // Aside from that, we set a flag for the socket to be closed when we're done. -    socket_should_be_closed_when_request_is_done_ = true; -    return; -  } - -  //Otherwise, sitll holding the mutex, we can shut everything down ourselves -  shutdown_ssl(socket_, true); -  shutdown_socket(socket_); -  close_socket(socket_); -} - -inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { -  connection_timeout_sec_ = sec; -  connection_timeout_usec_ = usec; -} - -inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { -  read_timeout_sec_ = sec; -  read_timeout_usec_ = usec; -} - -inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { -  write_timeout_sec_ = sec; -  write_timeout_usec_ = usec; -} - -inline void ClientImpl::set_basic_auth(const char *username, -                                       const char *password) { -  basic_auth_username_ = username; -  basic_auth_password_ = password; -} - -inline void ClientImpl::set_bearer_token_auth(const char *token) { -  bearer_token_auth_token_ = token; -} - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_digest_auth(const char *username, -                                        const char *password) { -  digest_auth_username_ = username; -  digest_auth_password_ = password; -} -#endif - -inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } - -inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } - -inline void ClientImpl::set_default_headers(Headers headers) { -  default_headers_ = std::move(headers); -} - -inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } - -inline void ClientImpl::set_socket_options(SocketOptions socket_options) { -  socket_options_ = std::move(socket_options); -} - -inline void ClientImpl::set_compress(bool on) { compress_ = on; } - -inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } - -inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } - -inline void ClientImpl::set_proxy(const char *host, int port) { -  proxy_host_ = host; -  proxy_port_ = port; -} - -inline void ClientImpl::set_proxy_basic_auth(const char *username, -                                             const char *password) { -  proxy_basic_auth_username_ = username; -  proxy_basic_auth_password_ = password; -} - -inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { -  proxy_bearer_token_auth_token_ = token; -} - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_proxy_digest_auth(const char *username, -                                              const char *password) { -  proxy_digest_auth_username_ = username; -  proxy_digest_auth_password_ = password; -} -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::enable_server_certificate_verification(bool enabled) { -  server_certificate_verification_ = enabled; -} -#endif - -inline void ClientImpl::set_logger(Logger logger) { -  logger_ = std::move(logger); -} - -/* - * SSL Implementation - */ -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -namespace detail { - -template <typename U, typename V> -inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, -                    U SSL_connect_or_accept, V setup) { -  SSL *ssl = nullptr; -  { -    std::lock_guard<std::mutex> guard(ctx_mutex); -    ssl = SSL_new(ctx); -  } - -  if (ssl) { -    auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE); -    SSL_set_bio(ssl, bio, bio); - -    if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { -      SSL_shutdown(ssl); -      { -        std::lock_guard<std::mutex> guard(ctx_mutex); -        SSL_free(ssl); -      } -      return nullptr; -    } -  } - -  return ssl; -} - -inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, -                       bool shutdown_gracefully) { -  // sometimes we may want to skip this to try to avoid SIGPIPE if we know -  // the remote has closed the network connection -  // Note that it is not always possible to avoid SIGPIPE, this is merely a best-efforts. -  if (shutdown_gracefully) { -    SSL_shutdown(ssl);  -  } - -  std::lock_guard<std::mutex> guard(ctx_mutex); -  SSL_free(ssl); -} - -template <typename T> -inline bool -process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, -                          time_t keep_alive_timeout_sec, -                          time_t read_timeout_sec, time_t read_timeout_usec, -                          time_t write_timeout_sec, time_t write_timeout_usec, -                          T callback) { -  return process_server_socket_core( -      sock, keep_alive_max_count, keep_alive_timeout_sec, -      [&](bool close_connection, bool &connection_closed) { -        SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, -                             write_timeout_sec, write_timeout_usec); -        return callback(strm, close_connection, connection_closed); -      }); -} - -template <typename T> -inline bool -process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, -                          time_t read_timeout_usec, time_t write_timeout_sec, -                          time_t write_timeout_usec, T callback) { -  SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, -                       write_timeout_sec, write_timeout_usec); -  return callback(strm); -} - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_; - -class SSLThreadLocks { -public: -  SSLThreadLocks() { -    openSSL_locks_ = -        std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks()); -    CRYPTO_set_locking_callback(locking_callback); -  } - -  ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } - -private: -  static void locking_callback(int mode, int type, const char * /*file*/, -                               int /*line*/) { -    auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)]; -    if (mode & CRYPTO_LOCK) { -      lk.lock(); -    } else { -      lk.unlock(); -    } -  } -}; - -#endif - -class SSLInit { -public: -  SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL -    SSL_load_error_strings(); -    SSL_library_init(); -#else -    OPENSSL_init_ssl( -        OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); -#endif -  } - -  ~SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL -    ERR_free_strings(); -#endif -  } - -private: -#if OPENSSL_VERSION_NUMBER < 0x10100000L -  SSLThreadLocks thread_init_; -#endif -}; - -// SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, -                                        time_t read_timeout_sec, -                                        time_t read_timeout_usec, -                                        time_t write_timeout_sec, -                                        time_t write_timeout_usec) -    : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), -      read_timeout_usec_(read_timeout_usec), -      write_timeout_sec_(write_timeout_sec), -      write_timeout_usec_(write_timeout_usec) { -  SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); -} - -inline SSLSocketStream::~SSLSocketStream() {} - -inline bool SSLSocketStream::is_readable() const { -  return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; -} - -inline bool SSLSocketStream::is_writable() const { -  return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > -         0; -} - -inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { -  if (SSL_pending(ssl_) > 0) { -    return SSL_read(ssl_, ptr, static_cast<int>(size)); -  } else if (is_readable()) { -    auto ret = SSL_read(ssl_, ptr, static_cast<int>(size)); -    if (ret < 0) { -      auto err = SSL_get_error(ssl_, ret); -      while (err == SSL_ERROR_WANT_READ) { -        if (SSL_pending(ssl_) > 0) { -          return SSL_read(ssl_, ptr, static_cast<int>(size)); -        } else if (is_readable()) { -          ret = SSL_read(ssl_, ptr, static_cast<int>(size)); -          if (ret >= 0) { -            return ret; -          } -          err = SSL_get_error(ssl_, ret); -        } else { -          return -1; -        } -      } -    } -    return ret; -  } -  return -1; -} - -inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { -  if (is_writable()) { return SSL_write(ssl_, ptr, static_cast<int>(size)); } -  return -1; -} - -inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, -                                                    int &port) const { -  detail::get_remote_ip_and_port(sock_, ip, port); -} - -static SSLInit sslinit_; - -} // namespace detail - -// SSL HTTP server implementation -inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, -                            const char *client_ca_cert_file_path, -                            const char *client_ca_cert_dir_path) { -  ctx_ = SSL_CTX_new(SSLv23_server_method()); - -  if (ctx_) { -    SSL_CTX_set_options(ctx_, -                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | -                            SSL_OP_NO_COMPRESSION | -                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - -    // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); -    // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); -    // EC_KEY_free(ecdh); - -    if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || -        SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != -            1) { -      SSL_CTX_free(ctx_); -      ctx_ = nullptr; -    } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { -      // if (client_ca_cert_file_path) { -      //   auto list = SSL_load_client_CA_file(client_ca_cert_file_path); -      //   SSL_CTX_set_client_CA_list(ctx_, list); -      // } - -      SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, -                                    client_ca_cert_dir_path); - -      SSL_CTX_set_verify( -          ctx_, -          SSL_VERIFY_PEER | -              SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, -          nullptr); -    } -  } -} - -inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, -                            X509_STORE *client_ca_cert_store) { -  ctx_ = SSL_CTX_new(SSLv23_server_method()); - -  if (ctx_) { -    SSL_CTX_set_options(ctx_, -                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | -                            SSL_OP_NO_COMPRESSION | -                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - -    if (SSL_CTX_use_certificate(ctx_, cert) != 1 || -        SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { -      SSL_CTX_free(ctx_); -      ctx_ = nullptr; -    } else if (client_ca_cert_store) { - -      SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); - -      SSL_CTX_set_verify( -          ctx_, -          SSL_VERIFY_PEER | -              SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, -          nullptr); -    } -  } -} - -inline SSLServer::~SSLServer() { -  if (ctx_) { SSL_CTX_free(ctx_); } -} - -inline bool SSLServer::is_valid() const { return ctx_; } - -inline bool SSLServer::process_and_close_socket(socket_t sock) { -  auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept, -                             [](SSL * /*ssl*/) { return true; }); - -  if (ssl) { -    auto ret = detail::process_server_socket_ssl( -        ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, -        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, -        write_timeout_usec_, -        [this, ssl](Stream &strm, bool close_connection, -                    bool &connection_closed) { -          return process_request(strm, close_connection, connection_closed, -                                 [&](Request &req) { req.ssl = ssl; }); -        }); - -    detail::ssl_delete(ctx_mutex_, ssl, ret); -    detail::shutdown_socket(sock); -    detail::close_socket(sock); -    return ret; -  } - -  detail::shutdown_socket(sock); -  detail::close_socket(sock); -  return false; -} - -// SSL HTTP client implementation -inline SSLClient::SSLClient(const std::string &host) -    : SSLClient(host, 443, std::string(), std::string()) {} - -inline SSLClient::SSLClient(const std::string &host, int port) -    : SSLClient(host, port, std::string(), std::string()) {} - -inline SSLClient::SSLClient(const std::string &host, int port, -                            const std::string &client_cert_path, -                            const std::string &client_key_path) -    : ClientImpl(host, port, client_cert_path, client_key_path) { -  ctx_ = SSL_CTX_new(SSLv23_client_method()); - -  detail::split(&host_[0], &host_[host_.size()], '.', -                [&](const char *b, const char *e) { -                  host_components_.emplace_back(std::string(b, e)); -                }); -  if (!client_cert_path.empty() && !client_key_path.empty()) { -    if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), -                                     SSL_FILETYPE_PEM) != 1 || -        SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), -                                    SSL_FILETYPE_PEM) != 1) { -      SSL_CTX_free(ctx_); -      ctx_ = nullptr; -    } -  } -} - -inline SSLClient::SSLClient(const std::string &host, int port, -                            X509 *client_cert, EVP_PKEY *client_key) -    : ClientImpl(host, port) { -  ctx_ = SSL_CTX_new(SSLv23_client_method()); - -  detail::split(&host_[0], &host_[host_.size()], '.', -                [&](const char *b, const char *e) { -                  host_components_.emplace_back(std::string(b, e)); -                }); -  if (client_cert != nullptr && client_key != nullptr) { -    if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || -        SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { -      SSL_CTX_free(ctx_); -      ctx_ = nullptr; -    } -  } -} - -inline SSLClient::~SSLClient() { -  if (ctx_) { SSL_CTX_free(ctx_); } -  // Make sure to shut down SSL since shutdown_ssl will resolve to the -  // base function rather than the derived function once we get to the -  // base class destructor, and won't free the SSL (causing a leak). -  SSLClient::shutdown_ssl(socket_, true); -} - -inline bool SSLClient::is_valid() const { return ctx_; } - -inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, -                                        const char *ca_cert_dir_path) { -  if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; } -  if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } -} - -inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { -  if (ca_cert_store) { -    if (ctx_) { -      if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { -        // Free memory allocated for old cert and use new store `ca_cert_store` -        SSL_CTX_set_cert_store(ctx_, ca_cert_store); -      } -    } else { -      X509_STORE_free(ca_cert_store); -    } -  } -} - -inline long SSLClient::get_openssl_verify_result() const { -  return verify_result_; -} - -inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } - -inline bool SSLClient::create_and_connect_socket(Socket &socket) { -  return is_valid() && ClientImpl::create_and_connect_socket(socket); -} - -// Assumes that socket_mutex_ is locked and that there are no requests in flight -inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, -                                          bool &success) { -  success = true; -  Response res2; -  if (!detail::process_client_socket( -          socket.sock, read_timeout_sec_, read_timeout_usec_, -          write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { -            Request req2; -            req2.method = "CONNECT"; -            req2.path = host_and_port_; -            return process_request(strm, req2, res2, false); -          })) { -    // Thread-safe to close everything because we are assuming there are no requests in flight -    shutdown_ssl(socket, true); -    shutdown_socket(socket); -    close_socket(socket); -    success = false; -    return false; -  } - -  if (res2.status == 407) { -    if (!proxy_digest_auth_username_.empty() && -        !proxy_digest_auth_password_.empty()) { -      std::map<std::string, std::string> auth; -      if (detail::parse_www_authenticate(res2, auth, true)) { -        Response res3; -        if (!detail::process_client_socket( -                socket.sock, read_timeout_sec_, read_timeout_usec_, -                write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { -                  Request req3; -                  req3.method = "CONNECT"; -                  req3.path = host_and_port_; -                  req3.headers.insert(detail::make_digest_authentication_header( -                      req3, auth, 1, detail::random_string(10), -                      proxy_digest_auth_username_, proxy_digest_auth_password_, -                      true)); -                  return process_request(strm, req3, res3, false); -                })) { -          // Thread-safe to close everything because we are assuming there are no requests in flight -          shutdown_ssl(socket, true); -          shutdown_socket(socket); -          close_socket(socket); -          success = false; -          return false; -        } -      } -    } else { -      res = res2; -      return false; -    } -  } - -  return true; -} - -inline bool SSLClient::load_certs() { -  bool ret = true; - -  std::call_once(initialize_cert_, [&]() { -    std::lock_guard<std::mutex> guard(ctx_mutex_); -    if (!ca_cert_file_path_.empty()) { -      if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), -                                         nullptr)) { -        ret = false; -      } -    } else if (!ca_cert_dir_path_.empty()) { -      if (!SSL_CTX_load_verify_locations(ctx_, nullptr, -                                         ca_cert_dir_path_.c_str())) { -        ret = false; -      } -    } else { -#ifdef _WIN32 -      detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#else -      SSL_CTX_set_default_verify_paths(ctx_); -#endif -    } -  }); - -  return ret; -} - -inline bool SSLClient::initialize_ssl(Socket &socket) { -  auto ssl = detail::ssl_new( -      socket.sock, ctx_, ctx_mutex_, -      [&](SSL *ssl) { -        if (server_certificate_verification_) { -          if (!load_certs()) { -            error_ = Error::SSLLoadingCerts; -            return false; -          } -          SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); -        } - -        if (SSL_connect(ssl) != 1) { -          error_ = Error::SSLConnection; -          return false; -        } - -        if (server_certificate_verification_) { -          verify_result_ = SSL_get_verify_result(ssl); - -          if (verify_result_ != X509_V_OK) { -            error_ = Error::SSLServerVerification; -            return false; -          } - -          auto server_cert = SSL_get_peer_certificate(ssl); - -          if (server_cert == nullptr) { -            error_ = Error::SSLServerVerification; -            return false; -          } - -          if (!verify_host(server_cert)) { -            X509_free(server_cert); -            error_ = Error::SSLServerVerification; -            return false; -          } -          X509_free(server_cert); -        } - -        return true; -      }, -      [&](SSL *ssl) { -        SSL_set_tlsext_host_name(ssl, host_.c_str()); -        return true; -      }); - -  if (ssl) { -    socket.ssl = ssl; -    return true; -  } - -  shutdown_socket(socket); -  close_socket(socket); -  return false; -} - -inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { -  if (socket.sock == INVALID_SOCKET) { -    assert(socket.ssl == nullptr); -    return; -  } -  if (socket.ssl) { -    detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); -    socket.ssl = nullptr; -  } -  assert(socket.ssl == nullptr); -} - -inline bool -SSLClient::process_socket(const Socket &socket, -                          std::function<bool(Stream &strm)> callback) { -  assert(socket.ssl); -  return detail::process_client_socket_ssl( -      socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, -      write_timeout_sec_, write_timeout_usec_, std::move(callback)); -} - -inline bool SSLClient::is_ssl() const { return true; } - -inline bool SSLClient::verify_host(X509 *server_cert) const { -  /* Quote from RFC2818 section 3.1 "Server Identity" - -     If a subjectAltName extension of type dNSName is present, that MUST -     be used as the identity. Otherwise, the (most specific) Common Name -     field in the Subject field of the certificate MUST be used. Although -     the use of the Common Name is existing practice, it is deprecated and -     Certification Authorities are encouraged to use the dNSName instead. - -     Matching is performed using the matching rules specified by -     [RFC2459].  If more than one identity of a given type is present in -     the certificate (e.g., more than one dNSName name, a match in any one -     of the set is considered acceptable.) Names may contain the wildcard -     character * which is considered to match any single domain name -     component or component fragment. E.g., *.a.com matches foo.a.com but -     not bar.foo.a.com. f*.com matches foo.com but not bar.com. - -     In some cases, the URI is specified as an IP address rather than a -     hostname. In this case, the iPAddress subjectAltName must be present -     in the certificate and must exactly match the IP in the URI. - -  */ -  return verify_host_with_subject_alt_name(server_cert) || -         verify_host_with_common_name(server_cert); -} - -inline bool -SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { -  auto ret = false; - -  auto type = GEN_DNS; - -  struct in6_addr addr6; -  struct in_addr addr; -  size_t addr_len = 0; - -#ifndef __MINGW32__ -  if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { -    type = GEN_IPADD; -    addr_len = sizeof(struct in6_addr); -  } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { -    type = GEN_IPADD; -    addr_len = sizeof(struct in_addr); -  } -#endif - -  auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>( -      X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); - -  if (alt_names) { -    auto dsn_matched = false; -    auto ip_mached = false; - -    auto count = sk_GENERAL_NAME_num(alt_names); - -    for (decltype(count) i = 0; i < count && !dsn_matched; i++) { -      auto val = sk_GENERAL_NAME_value(alt_names, i); -      if (val->type == type) { -        auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); -        auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); - -        if (strlen(name) == name_len) { -          switch (type) { -          case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; - -          case GEN_IPADD: -            if (!memcmp(&addr6, name, addr_len) || -                !memcmp(&addr, name, addr_len)) { -              ip_mached = true; -            } -            break; -          } -        } -      } -    } - -    if (dsn_matched || ip_mached) { ret = true; } -  } - -  GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); -  return ret; -} - -inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { -  const auto subject_name = X509_get_subject_name(server_cert); - -  if (subject_name != nullptr) { -    char name[BUFSIZ]; -    auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, -                                              name, sizeof(name)); - -    if (name_len != -1) { -      return check_host_name(name, static_cast<size_t>(name_len)); -    } -  } - -  return false; -} - -inline bool SSLClient::check_host_name(const char *pattern, -                                       size_t pattern_len) const { -  if (host_.size() == pattern_len && host_ == pattern) { return true; } - -  // Wildcard match -  // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 -  std::vector<std::string> pattern_components; -  detail::split(&pattern[0], &pattern[pattern_len], '.', -                [&](const char *b, const char *e) { -                  pattern_components.emplace_back(std::string(b, e)); -                }); - -  if (host_components_.size() != pattern_components.size()) { return false; } - -  auto itr = pattern_components.begin(); -  for (const auto &h : host_components_) { -    auto &p = *itr; -    if (p != h && p != "*") { -      auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && -                            !p.compare(0, p.size() - 1, h)); -      if (!partial_match) { return false; } -    } -    ++itr; -  } - -  return true; -} -#endif - -// Universal client implementation -inline Client::Client(const char *scheme_host_port) -    : Client(scheme_host_port, std::string(), std::string()) {} - -inline Client::Client(const char *scheme_host_port, -                      const std::string &client_cert_path, -                      const std::string &client_key_path) { -  const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); - -  std::cmatch m; -  if (std::regex_match(scheme_host_port, m, re)) { -    auto scheme = m[1].str(); - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -    if (!scheme.empty() && (scheme != "http" && scheme != "https")) { -#else -    if (!scheme.empty() && scheme != "http") { -#endif -      std::string msg = "'" + scheme + "' scheme is not supported."; -      throw std::invalid_argument(msg); -      return; -    } - -    auto is_ssl = scheme == "https"; - -    auto host = m[2].str(); - -    auto port_str = m[3].str(); -    auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); - -    if (is_ssl) { -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -      cli_ = detail::make_unique<SSLClient>(host.c_str(), port, -                                            client_cert_path, client_key_path); -      is_ssl_ = is_ssl; -#endif -    } else { -      cli_ = detail::make_unique<ClientImpl>(host.c_str(), port, -                                             client_cert_path, client_key_path); -    } -  } else { -    cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80, -                                           client_cert_path, client_key_path); -  } -} - -inline Client::Client(const std::string &host, int port) -    : cli_(detail::make_unique<ClientImpl>(host, port)) {} - -inline Client::Client(const std::string &host, int port, -                      const std::string &client_cert_path, -                      const std::string &client_key_path) -    : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path, -                                           client_key_path)) {} - -inline Client::~Client() {} - -inline bool Client::is_valid() const { -  return cli_ != nullptr && cli_->is_valid(); -} - -inline Result Client::Get(const char *path) { return cli_->Get(path); } -inline Result Client::Get(const char *path, const Headers &headers) { -  return cli_->Get(path, headers); -} -inline Result Client::Get(const char *path, Progress progress) { -  return cli_->Get(path, std::move(progress)); -} -inline Result Client::Get(const char *path, const Headers &headers, -                          Progress progress) { -  return cli_->Get(path, headers, std::move(progress)); -} -inline Result Client::Get(const char *path, ContentReceiver content_receiver) { -  return cli_->Get(path, std::move(content_receiver)); -} -inline Result Client::Get(const char *path, const Headers &headers, -                          ContentReceiver content_receiver) { -  return cli_->Get(path, headers, std::move(content_receiver)); -} -inline Result Client::Get(const char *path, ContentReceiver content_receiver, -                          Progress progress) { -  return cli_->Get(path, std::move(content_receiver), std::move(progress)); -} -inline Result Client::Get(const char *path, const Headers &headers, -                          ContentReceiver content_receiver, Progress progress) { -  return cli_->Get(path, headers, std::move(content_receiver), -                   std::move(progress)); -} -inline Result Client::Get(const char *path, ResponseHandler response_handler, -                          ContentReceiver content_receiver) { -  return cli_->Get(path, std::move(response_handler), -                   std::move(content_receiver)); -} -inline Result Client::Get(const char *path, const Headers &headers, -                          ResponseHandler response_handler, -                          ContentReceiver content_receiver) { -  return cli_->Get(path, headers, std::move(response_handler), -                   std::move(content_receiver)); -} -inline Result Client::Get(const char *path, ResponseHandler response_handler, -                          ContentReceiver content_receiver, Progress progress) { -  return cli_->Get(path, std::move(response_handler), -                   std::move(content_receiver), std::move(progress)); -} -inline Result Client::Get(const char *path, const Headers &headers, -                          ResponseHandler response_handler, -                          ContentReceiver content_receiver, Progress progress) { -  return cli_->Get(path, headers, std::move(response_handler), -                   std::move(content_receiver), std::move(progress)); -} - -inline Result Client::Head(const char *path) { return cli_->Head(path); } -inline Result Client::Head(const char *path, const Headers &headers) { -  return cli_->Head(path, headers); -} - -inline Result Client::Post(const char *path) { return cli_->Post(path); } -inline Result Client::Post(const char *path, const std::string &body, -                           const char *content_type) { -  return cli_->Post(path, body, content_type); -} -inline Result Client::Post(const char *path, const Headers &headers, -                           const std::string &body, const char *content_type) { -  return cli_->Post(path, headers, body, content_type); -} -inline Result Client::Post(const char *path, size_t content_length, -                           ContentProvider content_provider, -                           const char *content_type) { -  return cli_->Post(path, content_length, std::move(content_provider), -                    content_type); -} -inline Result Client::Post(const char *path, const Headers &headers, -                           size_t content_length, -                           ContentProvider content_provider, -                           const char *content_type) { -  return cli_->Post(path, headers, content_length, std::move(content_provider), -                    content_type); -} -inline Result Client::Post(const char *path, const Params ¶ms) { -  return cli_->Post(path, params); -} -inline Result Client::Post(const char *path, const Headers &headers, -                           const Params ¶ms) { -  return cli_->Post(path, headers, params); -} -inline Result Client::Post(const char *path, -                           const MultipartFormDataItems &items) { -  return cli_->Post(path, items); -} -inline Result Client::Post(const char *path, const Headers &headers, -                           const MultipartFormDataItems &items) { -  return cli_->Post(path, headers, items); -} -inline Result Client::Post(const char *path, const Headers &headers, -                           const MultipartFormDataItems &items, -                           const std::string &boundary) { -  return cli_->Post(path, headers, items, boundary); -} -inline Result Client::Put(const char *path) { return cli_->Put(path); } -inline Result Client::Put(const char *path, const std::string &body, -                          const char *content_type) { -  return cli_->Put(path, body, content_type); -} -inline Result Client::Put(const char *path, const Headers &headers, -                          const std::string &body, const char *content_type) { -  return cli_->Put(path, headers, body, content_type); -} -inline Result Client::Put(const char *path, size_t content_length, -                          ContentProvider content_provider, -                          const char *content_type) { -  return cli_->Put(path, content_length, std::move(content_provider), -                   content_type); -} -inline Result Client::Put(const char *path, const Headers &headers, -                          size_t content_length, -                          ContentProvider content_provider, -                          const char *content_type) { -  return cli_->Put(path, headers, content_length, std::move(content_provider), -                   content_type); -} -inline Result Client::Put(const char *path, const Params ¶ms) { -  return cli_->Put(path, params); -} -inline Result Client::Put(const char *path, const Headers &headers, -                          const Params ¶ms) { -  return cli_->Put(path, headers, params); -} -inline Result Client::Patch(const char *path, const std::string &body, -                            const char *content_type) { -  return cli_->Patch(path, body, content_type); -} -inline Result Client::Patch(const char *path, const Headers &headers, -                            const std::string &body, const char *content_type) { -  return cli_->Patch(path, headers, body, content_type); -} -inline Result Client::Patch(const char *path, size_t content_length, -                            ContentProvider content_provider, -                            const char *content_type) { -  return cli_->Patch(path, content_length, std::move(content_provider), -                     content_type); -} -inline Result Client::Patch(const char *path, const Headers &headers, -                            size_t content_length, -                            ContentProvider content_provider, -                            const char *content_type) { -  return cli_->Patch(path, headers, content_length, std::move(content_provider), -                     content_type); -} -inline Result Client::Delete(const char *path) { return cli_->Delete(path); } -inline Result Client::Delete(const char *path, const std::string &body, -                             const char *content_type) { -  return cli_->Delete(path, body, content_type); -} -inline Result Client::Delete(const char *path, const Headers &headers) { -  return cli_->Delete(path, headers); -} -inline Result Client::Delete(const char *path, const Headers &headers, -                             const std::string &body, -                             const char *content_type) { -  return cli_->Delete(path, headers, body, content_type); -} -inline Result Client::Options(const char *path) { return cli_->Options(path); } -inline Result Client::Options(const char *path, const Headers &headers) { -  return cli_->Options(path, headers); -} - -inline bool Client::send(const Request &req, Response &res) { -  return cli_->send(req, res); -} - -inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } - -inline void Client::stop() { cli_->stop(); } - -inline void Client::set_default_headers(Headers headers) { -  cli_->set_default_headers(std::move(headers)); -} - -inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } -inline void Client::set_socket_options(SocketOptions socket_options) { -  cli_->set_socket_options(std::move(socket_options)); -} - -inline void Client::set_connection_timeout(time_t sec, time_t usec) { -  cli_->set_connection_timeout(sec, usec); -} -inline void Client::set_read_timeout(time_t sec, time_t usec) { -  cli_->set_read_timeout(sec, usec); -} -inline void Client::set_write_timeout(time_t sec, time_t usec) { -  cli_->set_write_timeout(sec, usec); -} - -inline void Client::set_basic_auth(const char *username, const char *password) { -  cli_->set_basic_auth(username, password); -} -inline void Client::set_bearer_token_auth(const char *token) { -  cli_->set_bearer_token_auth(token); -} -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_digest_auth(const char *username, -                                    const char *password) { -  cli_->set_digest_auth(username, password); -} -#endif - -inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } -inline void Client::set_follow_location(bool on) { -  cli_->set_follow_location(on); -} - -inline void Client::set_compress(bool on) { cli_->set_compress(on); } - -inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } - -inline void Client::set_interface(const char *intf) { -  cli_->set_interface(intf); -} - -inline void Client::set_proxy(const char *host, int port) { -  cli_->set_proxy(host, port); -} -inline void Client::set_proxy_basic_auth(const char *username, -                                         const char *password) { -  cli_->set_proxy_basic_auth(username, password); -} -inline void Client::set_proxy_bearer_token_auth(const char *token) { -  cli_->set_proxy_bearer_token_auth(token); -} -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_proxy_digest_auth(const char *username, -                                          const char *password) { -  cli_->set_proxy_digest_auth(username, password); -} -#endif - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::enable_server_certificate_verification(bool enabled) { -  cli_->enable_server_certificate_verification(enabled); -} -#endif - -inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_ca_cert_path(const char *ca_cert_file_path, -                                     const char *ca_cert_dir_path) { -  if (is_ssl_) { -    static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path, -                                                     ca_cert_dir_path); -  } -} - -inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { -  if (is_ssl_) { -    static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store); -  } -} - -inline long Client::get_openssl_verify_result() const { -  if (is_ssl_) { -    return static_cast<SSLClient &>(*cli_).get_openssl_verify_result(); -  } -  return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? -} - -inline SSL_CTX *Client::ssl_context() const { -  if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); } -  return nullptr; -} -#endif - -// ---------------------------------------------------------------------------- - -} // namespace httplib - -#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 9a30b1e2a..151ddc462 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -1,4 +1,4 @@ -if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")) +if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)      set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE)      set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index a0ae07752..d25a1a645 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(audio_core STATIC      voice_context.h      $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> +    $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>  )  create_target_directory_groups(audio_core) @@ -71,3 +72,7 @@ if(ENABLE_CUBEB)      target_link_libraries(audio_core PRIVATE cubeb)      target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)  endif() +if(ENABLE_SDL2) +    target_link_libraries(audio_core PRIVATE SDL2) +    target_compile_definitions(audio_core PRIVATE HAVE_SDL2) +endif() diff --git a/src/audio_core/common.h b/src/audio_core/common.h index fe546c55d..1ab537588 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -15,7 +15,7 @@ constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};  constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};  } // namespace Audren -constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); +constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '9');  constexpr std::size_t MAX_MIX_BUFFERS = 24;  constexpr std::size_t MAX_BIQUAD_FILTERS = 2;  constexpr std::size_t MAX_CHANNEL_COUNT = 6; diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp new file mode 100644 index 000000000..62d3716a6 --- /dev/null +++ b/src/audio_core/sdl2_sink.cpp @@ -0,0 +1,163 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <atomic> +#include <cstring> +#include "audio_core/sdl2_sink.h" +#include "audio_core/stream.h" +#include "audio_core/time_stretch.h" +#include "common/assert.h" +#include "common/logging/log.h" +//#include "common/settings.h" + +// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif +#include <SDL.h> +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace AudioCore { + +class SDLSinkStream final : public SinkStream { +public: +    SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) +        : num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} { + +        SDL_AudioSpec spec; +        spec.freq = sample_rate; +        spec.channels = static_cast<u8>(num_channels); +        spec.format = AUDIO_S16SYS; +        spec.samples = 4096; +        spec.callback = nullptr; + +        SDL_AudioSpec obtained; +        if (output_device.empty()) { +            dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); +        } else { +            dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); +        } + +        if (dev == 0) { +            LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); +            return; +        } + +        SDL_PauseAudioDevice(dev, 0); +    } + +    ~SDLSinkStream() override { +        if (dev == 0) { +            return; +        } + +        SDL_CloseAudioDevice(dev); +    } + +    void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { +        if (source_num_channels > num_channels) { +            // Downsample 6 channels to 2 +            ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); + +            std::vector<s16> buf; +            buf.reserve(samples.size() * num_channels / source_num_channels); +            for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { +                // Downmixing implementation taken from the ATSC standard +                const s16 left{samples[i + 0]}; +                const s16 right{samples[i + 1]}; +                const s16 center{samples[i + 2]}; +                const s16 surround_left{samples[i + 4]}; +                const s16 surround_right{samples[i + 5]}; +                // Not used in the ATSC reference implementation +                [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; + +                constexpr s32 clev{707}; // center mixing level coefficient +                constexpr s32 slev{707}; // surround mixing level coefficient + +                buf.push_back(static_cast<s16>(left + (clev * center / 1000) + +                                               (slev * surround_left / 1000))); +                buf.push_back(static_cast<s16>(right + (clev * center / 1000) + +                                               (slev * surround_right / 1000))); +            } +            int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()), +                                     static_cast<u32>(buf.size() * sizeof(s16))); +            if (ret < 0) +                LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); +            return; +        } + +        int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()), +                                 static_cast<u32>(samples.size() * sizeof(s16))); +        if (ret < 0) +            LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); +    } + +    std::size_t SamplesInQueue(u32 channel_count) const override { +        if (dev == 0) +            return 0; + +        return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); +    } + +    void Flush() override { +        should_flush = true; +    } + +    u32 GetNumChannels() const { +        return num_channels; +    } + +private: +    SDL_AudioDeviceID dev = 0; +    u32 num_channels{}; +    std::atomic<bool> should_flush{}; +    TimeStretcher time_stretch; +}; + +SDLSink::SDLSink(std::string_view target_device_name) { +    if (!SDL_WasInit(SDL_INIT_AUDIO)) { +        if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { +            LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); +            return; +        } +    } + +    if (target_device_name != auto_device_name && !target_device_name.empty()) { +        output_device = target_device_name; +    } else { +        output_device.clear(); +    } +} + +SDLSink::~SDLSink() = default; + +SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { +    sink_streams.push_back( +        std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device)); +    return *sink_streams.back(); +} + +std::vector<std::string> ListSDLSinkDevices() { +    std::vector<std::string> device_list; + +    if (!SDL_WasInit(SDL_INIT_AUDIO)) { +        if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { +            LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); +            return {}; +        } +    } + +    const int device_count = SDL_GetNumAudioDevices(0); +    for (int i = 0; i < device_count; ++i) { +        device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); +    } + +    return device_list; +} + +} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h new file mode 100644 index 000000000..8ec1526d8 --- /dev/null +++ b/src/audio_core/sdl2_sink.h @@ -0,0 +1,29 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <vector> + +#include "audio_core/sink.h" + +namespace AudioCore { + +class SDLSink final : public Sink { +public: +    explicit SDLSink(std::string_view device_id); +    ~SDLSink() override; + +    SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, +                                  const std::string& name) override; + +private: +    std::string output_device; +    std::vector<SinkStreamPtr> sink_streams; +}; + +std::vector<std::string> ListSDLSinkDevices(); + +} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h index 66ee4e8a0..9e2b69785 100644 --- a/src/audio_core/sink_context.h +++ b/src/audio_core/sink_context.h @@ -4,6 +4,8 @@  #pragma once +#include <array> +#include <vector>  #include "audio_core/common.h"  #include "common/common_funcs.h"  #include "common/common_types.h" diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index a848eb1c9..de10aecd2 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -11,6 +11,9 @@  #ifdef HAVE_CUBEB  #include "audio_core/cubeb_sink.h"  #endif +#ifdef HAVE_SDL2 +#include "audio_core/sdl2_sink.h" +#endif  #include "common/logging/log.h"  namespace AudioCore { @@ -36,6 +39,13 @@ constexpr SinkDetails sink_details[] = {                  },                  &ListCubebSinkDevices},  #endif +#ifdef HAVE_SDL2 +    SinkDetails{"sdl2", +                [](std::string_view device_id) -> std::unique_ptr<Sink> { +                    return std::make_unique<SDLSink>(device_id); +                }, +                &ListSDLSinkDevices}, +#endif      SinkDetails{"null",                  [](std::string_view device_id) -> std::unique_ptr<Sink> {                      return std::make_unique<NullSink>(device_id); diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index ad6c587c2..5a30f55a7 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -107,9 +107,12 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {      active_buffer = queued_buffers.front();      queued_buffers.pop(); -    VolumeAdjustSamples(active_buffer->GetSamples(), game_volume); +    auto& samples = active_buffer->GetSamples(); -    sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); +    VolumeAdjustSamples(samples, game_volume); + +    sink_stream.EnqueueSamples(GetNumChannels(), samples); +    played_samples += samples.size();      const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer); diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 559844b9b..dbd97ec9c 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -89,6 +89,11 @@ public:          return sample_rate;      } +    /// Gets the number of samples played so far +    [[nodiscard]] u64 GetPlayedSampleCount() const { +        return played_samples; +    } +      /// Gets the number of channels      [[nodiscard]] u32 GetNumChannels() const; @@ -106,6 +111,7 @@ private:      [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;      u32 sample_rate;                  ///< Sample rate of the stream +    u64 played_samples{};             ///< The current played sample count      Format format;                    ///< Format of the stream      float game_volume = 1.0f;         ///< The volume the game currently has set      ReleaseCallback release_callback; ///< Buffer release callback for the stream diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 97fbdcbf9..a6fa9a85d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -110,7 +110,6 @@ add_library(common STATIC      cityhash.cpp      cityhash.h      common_funcs.h -    common_sizes.h      common_types.h      concepts.h      div_ceil.h @@ -134,6 +133,7 @@ add_library(common STATIC      host_memory.cpp      host_memory.h      intrusive_red_black_tree.h +    literals.h      logging/backend.cpp      logging/backend.h      logging/filter.cpp @@ -141,6 +141,7 @@ add_library(common STATIC      logging/log.h      logging/text_formatter.cpp      logging/text_formatter.h +    logging/types.h      lz4_compression.cpp      lz4_compression.h      math_util.h diff --git a/src/common/common_sizes.h b/src/common/common_sizes.h deleted file mode 100644 index 7e9fd968b..000000000 --- a/src/common/common_sizes.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <limits> - -#include "common/common_types.h" - -namespace Common { - -enum : u64 { -    Size_1_KB = 0x400ULL, -    Size_64_KB = 64ULL * Size_1_KB, -    Size_128_KB = 128ULL * Size_1_KB, -    Size_1_MB = 0x100000ULL, -    Size_2_MB = 2ULL * Size_1_MB, -    Size_4_MB = 4ULL * Size_1_MB, -    Size_5_MB = 5ULL * Size_1_MB, -    Size_14_MB = 14ULL * Size_1_MB, -    Size_32_MB = 32ULL * Size_1_MB, -    Size_33_MB = 33ULL * Size_1_MB, -    Size_128_MB = 128ULL * Size_1_MB, -    Size_448_MB = 448ULL * Size_1_MB, -    Size_507_MB = 507ULL * Size_1_MB, -    Size_562_MB = 562ULL * Size_1_MB, -    Size_1554_MB = 1554ULL * Size_1_MB, -    Size_2048_MB = 2048ULL * Size_1_MB, -    Size_2193_MB = 2193ULL * Size_1_MB, -    Size_3285_MB = 3285ULL * Size_1_MB, -    Size_4916_MB = 4916ULL * Size_1_MB, -    Size_1_GB = 0x40000000ULL, -    Size_2_GB = 2ULL * Size_1_GB, -    Size_4_GB = 4ULL * Size_1_GB, -    Size_6_GB = 6ULL * Size_1_GB, -    Size_8_GB = 8ULL * Size_1_GB, -    Size_64_GB = 64ULL * Size_1_GB, -    Size_512_GB = 512ULL * Size_1_GB, -    Size_Invalid = std::numeric_limits<u64>::max(), -}; - -} // namespace Common diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp index f2b4939df..c1362631e 100644 --- a/src/common/detached_tasks.cpp +++ b/src/common/detached_tasks.cpp @@ -21,6 +21,8 @@ void DetachedTasks::WaitForAllTasks() {  }  DetachedTasks::~DetachedTasks() { +    WaitForAllTasks(); +      std::unique_lock lock{mutex};      ASSERT(count == 0);      instance = nullptr; diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 9f3de1cb0..077f34995 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -172,7 +172,7 @@ std::string ReadStringFromFile(const std::filesystem::path& path, FileType type)  size_t WriteStringToFile(const std::filesystem::path& path, FileType type,                           std::string_view string) { -    if (!IsFile(path)) { +    if (Exists(path) && !IsFile(path)) {          return 0;      } @@ -183,11 +183,7 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type,  size_t AppendStringToFile(const std::filesystem::path& path, FileType type,                            std::string_view string) { -    if (!Exists(path)) { -        return WriteStringToFile(path, type, string); -    } - -    if (!IsFile(path)) { +    if (Exists(path) && !IsFile(path)) {          return 0;      } @@ -309,7 +305,11 @@ bool IOFile::Flush() const {      errno = 0; -    const auto flush_result = std::fflush(file) == 0; +#ifdef _WIN32 +    const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; +#else +    const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; +#endif      if (!flush_result) {          const auto ec = std::error_code{errno, std::generic_category()}; diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 50e270c5b..588fe619d 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -49,7 +49,7 @@ void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::op  /**   * Reads an entire file at path and returns a string of the contents read from the file. - * If the filesystem object at path is not a file, this function returns an empty string. + * If the filesystem object at path is not a regular file, this function returns an empty string.   *   * @param path Filesystem path   * @param type File type @@ -71,8 +71,9 @@ template <typename Path>  /**   * Writes a string to a file at path and returns the number of characters successfully written. - * If an file already exists at path, its contents will be erased. - * If the filesystem object at path is not a file, this function returns 0. + * If a file already exists at path, its contents will be erased. + * If a file does not exist at path, it creates and opens a new empty file for writing. + * If the filesystem object at path exists and is not a regular file, this function returns 0.   *   * @param path Filesystem path   * @param type File type @@ -95,8 +96,8 @@ template <typename Path>  /**   * Appends a string to a file at path and returns the number of characters successfully written. - * If a file does not exist at path, WriteStringToFile is called instead. - * If the filesystem object at path is not a file, this function returns 0. + * If a file does not exist at path, it creates and opens a new empty file for appending. + * If the filesystem object at path exists and is not a regular file, this function returns 0.   *   * @param path Filesystem path   * @param type File type @@ -395,11 +396,11 @@ public:      [[nodiscard]] size_t WriteString(std::span<const char> string) const;      /** -     * Flushes any unwritten buffered data into the file. +     * Attempts to flush any unwritten buffered data into the file and flush the file into the disk.       *       * @returns True if the flush was successful, false otherwise.       */ -    [[nodiscard]] bool Flush() const; +    bool Flush() const;      /**       * Resizes the file to a given size. diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index d492480d9..9089cad67 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -135,8 +135,9 @@ std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, File          return nullptr;      } -    if (!IsFile(path)) { -        LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", +    if (Exists(path) && !IsFile(path)) { +        LOG_ERROR(Common_Filesystem, +                  "Filesystem object at path={} exists and is not a regular file",                    PathToUTF8String(path));          return nullptr;      } @@ -321,7 +322,8 @@ bool RemoveDirContentsRecursively(const fs::path& path) {      std::error_code ec; -    for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { +    // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. +    for (const auto& entry : fs::directory_iterator(path, ec)) {          if (ec) {              LOG_ERROR(Common_Filesystem,                        "Failed to completely enumerate the directory at path={}, ec_message={}", @@ -337,6 +339,12 @@ bool RemoveDirContentsRecursively(const fs::path& path) {                        PathToUTF8String(entry.path()), ec.message());              break;          } + +        // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. +        // recursive_directory_iterator throws an exception despite passing in a std::error_code. +        if (entry.status().type() == fs::file_type::directory) { +            return RemoveDirContentsRecursively(entry.path()); +        }      }      if (ec) { @@ -475,7 +483,8 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,      std::error_code ec; -    for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { +    // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. +    for (const auto& entry : fs::directory_iterator(path, ec)) {          if (ec) {              break;          } @@ -495,6 +504,12 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,                  break;              }          } + +        // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. +        // recursive_directory_iterator throws an exception despite passing in a std::error_code. +        if (entry.status().type() == fs::file_type::directory) { +            IterateDirEntriesRecursively(entry.path(), callback, filter); +        }      }      if (callback_error || ec) { diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h index f6f256349..183126de3 100644 --- a/src/common/fs/fs.h +++ b/src/common/fs/fs.h @@ -48,18 +48,18 @@ template <typename Path>   *   * Failures occur when:   * - Input path is not valid - * - Filesystem object at path is not a file + * - Filesystem object at path is not a regular file   * - Filesystem at path is read only   *   * @param path Filesystem path   *   * @returns True if file removal succeeds or file does not exist, false otherwise.   */ -[[nodiscard]] bool RemoveFile(const std::filesystem::path& path); +bool RemoveFile(const std::filesystem::path& path);  #ifdef _WIN32  template <typename Path> -[[nodiscard]] bool RemoveFile(const Path& path) { +bool RemoveFile(const Path& path) {      if constexpr (IsChar<typename Path::value_type>) {          return RemoveFile(ToU8String(path));      } else { @@ -74,7 +74,7 @@ template <typename Path>   * Failures occur when:   * - One or both input path(s) is not valid   * - Filesystem object at old_path does not exist - * - Filesystem object at old_path is not a file + * - Filesystem object at old_path is not a regular file   * - Filesystem object at new_path exists   * - Filesystem at either path is read only   * @@ -110,8 +110,8 @@ template <typename Path1, typename Path2>   *   * Failures occur when:   * - Input path is not valid - * - Filesystem object at path is not a file - * - The file is not opened + * - Filesystem object at path exists and is not a regular file + * - The file is not open   *   * @param path Filesystem path   * @param mode File access mode @@ -251,11 +251,11 @@ template <typename Path>   *   * @returns True if directory removal succeeds or directory does not exist, false otherwise.   */ -[[nodiscard]] bool RemoveDir(const std::filesystem::path& path); +bool RemoveDir(const std::filesystem::path& path);  #ifdef _WIN32  template <typename Path> -[[nodiscard]] bool RemoveDir(const Path& path) { +bool RemoveDir(const Path& path) {      if constexpr (IsChar<typename Path::value_type>) {          return RemoveDir(ToU8String(path));      } else { @@ -276,11 +276,11 @@ template <typename Path>   *   * @returns True if the directory and all of its contents are removed successfully, false otherwise.   */ -[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); +bool RemoveDirRecursively(const std::filesystem::path& path);  #ifdef _WIN32  template <typename Path> -[[nodiscard]] bool RemoveDirRecursively(const Path& path) { +bool RemoveDirRecursively(const Path& path) {      if constexpr (IsChar<typename Path::value_type>) {          return RemoveDirRecursively(ToU8String(path));      } else { @@ -301,11 +301,11 @@ template <typename Path>   *   * @returns True if all of the directory's contents are removed successfully, false otherwise.   */ -[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); +bool RemoveDirContentsRecursively(const std::filesystem::path& path);  #ifdef _WIN32  template <typename Path> -[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { +bool RemoveDirContentsRecursively(const Path& path) {      if constexpr (IsChar<typename Path::value_type>) {          return RemoveDirContentsRecursively(ToU8String(path));      } else { @@ -435,11 +435,13 @@ template <typename Path>  #endif  /** - * Returns whether a filesystem object at path is a file. + * Returns whether a filesystem object at path is a regular file. + * A regular file is a file that stores text or binary data. + * It is not a directory, symlink, FIFO, socket, block device, or character device.   *   * @param path Filesystem path   * - * @returns True if a filesystem object at path is a file, false otherwise. + * @returns True if a filesystem object at path is a regular file, false otherwise.   */  [[nodiscard]] bool IsFile(const std::filesystem::path& path); diff --git a/src/common/hex_util.h b/src/common/hex_util.h index a8d414fb8..f5f9e4507 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -53,8 +53,9 @@ template <typename ContiguousContainer>      std::string out;      out.reserve(std::size(data) * pad_width); +    const auto format_str = fmt::runtime(upper ? "{:02X}" : "{:02x}");      for (const u8 c : data) { -        out += fmt::format(upper ? "{:02X}" : "{:02x}", c); +        out += fmt::format(format_str, c);      }      return out; diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 8bd70abc7..2a5a7596c 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -34,7 +34,7 @@ constexpr size_t HugePageSize = 0x200000;  // Manually imported for MinGW compatibility  #ifndef MEM_RESERVE_PLACEHOLDER -#define MEM_RESERVE_PLACEHOLDER 0x0004000 +#define MEM_RESERVE_PLACEHOLDER 0x00040000  #endif  #ifndef MEM_REPLACE_PLACEHOLDER  #define MEM_REPLACE_PLACEHOLDER 0x00004000 diff --git a/src/common/literals.h b/src/common/literals.h new file mode 100644 index 000000000..d55fed40b --- /dev/null +++ b/src/common/literals.h @@ -0,0 +1,31 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Common::Literals { + +constexpr u64 operator""_KiB(unsigned long long int x) { +    return 1024ULL * x; +} + +constexpr u64 operator""_MiB(unsigned long long int x) { +    return 1024_KiB * x; +} + +constexpr u64 operator""_GiB(unsigned long long int x) { +    return 1024_MiB * x; +} + +constexpr u64 operator""_TiB(unsigned long long int x) { +    return 1024_GiB * x; +} + +constexpr u64 operator""_PiB(unsigned long long int x) { +    return 1024_TiB * x; +} + +} // namespace Common::Literals diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 6aa8ac960..b6fa4affb 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -17,7 +17,10 @@  #endif  #include "common/assert.h" +#include "common/fs/file.h"  #include "common/fs/fs.h" +#include "common/literals.h" +  #include "common/logging/backend.h"  #include "common/logging/log.h"  #include "common/logging/text_formatter.h" @@ -97,8 +100,8 @@ private:                  write_logs(entry);              } -            // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case -            // where a system is repeatedly spamming logs even on close. +            // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a +            // case where a system is repeatedly spamming logs even on close.              const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;              int logs_written = 0;              while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { @@ -140,10 +143,14 @@ private:      std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};  }; +ConsoleBackend::~ConsoleBackend() = default; +  void ConsoleBackend::Write(const Entry& entry) {      PrintMessage(entry);  } +ColorConsoleBackend::~ColorConsoleBackend() = default; +  void ColorConsoleBackend::Write(const Entry& entry) {      PrintColoredMessage(entry);  } @@ -154,19 +161,23 @@ FileBackend::FileBackend(const std::filesystem::path& filename) {      // Existence checks are done within the functions themselves.      // We don't particularly care if these succeed or not. -    void(FS::RemoveFile(old_filename)); +    FS::RemoveFile(old_filename);      void(FS::RenameFile(filename, old_filename)); -    file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); +    file = +        std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);  } +FileBackend::~FileBackend() = default; +  void FileBackend::Write(const Entry& entry) { +    using namespace Common::Literals;      // prevent logs from going over the maximum size (in case its spamming and the user doesn't      // know) -    constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; -    constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; +    constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB; +    constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB; -    if (!file.IsOpen()) { +    if (!file->IsOpen()) {          return;      } @@ -176,147 +187,20 @@ void FileBackend::Write(const Entry& entry) {          return;      } -    bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); +    bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));      if (entry.log_level >= Level::Error) { -        void(file.Flush()); +        file->Flush();      }  } +DebuggerBackend::~DebuggerBackend() = default; +  void DebuggerBackend::Write(const Entry& entry) {  #ifdef _WIN32      ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());  #endif  } -/// 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, ARM)                                                                                 \ -    SUB(Core, Timing)                                                                              \ -    CLS(Config)                                                                                    \ -    CLS(Debug)                                                                                     \ -    SUB(Debug, Emulated)                                                                           \ -    SUB(Debug, GPU)                                                                                \ -    SUB(Debug, Breakpoint)                                                                         \ -    SUB(Debug, GDBStub)                                                                            \ -    CLS(Kernel)                                                                                    \ -    SUB(Kernel, SVC)                                                                               \ -    CLS(Service)                                                                                   \ -    SUB(Service, ACC)                                                                              \ -    SUB(Service, Audio)                                                                            \ -    SUB(Service, AM)                                                                               \ -    SUB(Service, AOC)                                                                              \ -    SUB(Service, APM)                                                                              \ -    SUB(Service, ARP)                                                                              \ -    SUB(Service, BCAT)                                                                             \ -    SUB(Service, BPC)                                                                              \ -    SUB(Service, BGTC)                                                                             \ -    SUB(Service, BTDRV)                                                                            \ -    SUB(Service, BTM)                                                                              \ -    SUB(Service, Capture)                                                                          \ -    SUB(Service, ERPT)                                                                             \ -    SUB(Service, ETicket)                                                                          \ -    SUB(Service, EUPLD)                                                                            \ -    SUB(Service, Fatal)                                                                            \ -    SUB(Service, FGM)                                                                              \ -    SUB(Service, Friend)                                                                           \ -    SUB(Service, FS)                                                                               \ -    SUB(Service, GRC)                                                                              \ -    SUB(Service, HID)                                                                              \ -    SUB(Service, IRS)                                                                              \ -    SUB(Service, LBL)                                                                              \ -    SUB(Service, LDN)                                                                              \ -    SUB(Service, LDR)                                                                              \ -    SUB(Service, LM)                                                                               \ -    SUB(Service, Migration)                                                                        \ -    SUB(Service, Mii)                                                                              \ -    SUB(Service, MM)                                                                               \ -    SUB(Service, NCM)                                                                              \ -    SUB(Service, NFC)                                                                              \ -    SUB(Service, NFP)                                                                              \ -    SUB(Service, NIFM)                                                                             \ -    SUB(Service, NIM)                                                                              \ -    SUB(Service, NPNS)                                                                             \ -    SUB(Service, NS)                                                                               \ -    SUB(Service, NVDRV)                                                                            \ -    SUB(Service, OLSC)                                                                             \ -    SUB(Service, PCIE)                                                                             \ -    SUB(Service, PCTL)                                                                             \ -    SUB(Service, PCV)                                                                              \ -    SUB(Service, PM)                                                                               \ -    SUB(Service, PREPO)                                                                            \ -    SUB(Service, PSC)                                                                              \ -    SUB(Service, PSM)                                                                              \ -    SUB(Service, SET)                                                                              \ -    SUB(Service, SM)                                                                               \ -    SUB(Service, SPL)                                                                              \ -    SUB(Service, SSL)                                                                              \ -    SUB(Service, TCAP)                                                                             \ -    SUB(Service, Time)                                                                             \ -    SUB(Service, USB)                                                                              \ -    SUB(Service, VI)                                                                               \ -    SUB(Service, WLAN)                                                                             \ -    CLS(HW)                                                                                        \ -    SUB(HW, Memory)                                                                                \ -    SUB(HW, LCD)                                                                                   \ -    SUB(HW, GPU)                                                                                   \ -    SUB(HW, AES)                                                                                   \ -    CLS(IPC)                                                                                       \ -    CLS(Frontend)                                                                                  \ -    CLS(Render)                                                                                    \ -    SUB(Render, Software)                                                                          \ -    SUB(Render, OpenGL)                                                                            \ -    SUB(Render, Vulkan)                                                                            \ -    CLS(Audio)                                                                                     \ -    SUB(Audio, DSP)                                                                                \ -    SUB(Audio, Sink)                                                                               \ -    CLS(Input)                                                                                     \ -    CLS(Network)                                                                                   \ -    CLS(Loader)                                                                                    \ -    CLS(CheatEngine)                                                                               \ -    CLS(Crypto)                                                                                    \ -    CLS(WebService) - -// GetClassName is a macro defined by Windows.h, grrr... -const char* 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 -    case Class::Count: -        break; -    } -    return "Invalid"; -} - -const char* 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); -    case Level::Count: -        break; -    } -#undef LVL -    return "Invalid"; -} -  void SetGlobalFilter(const Filter& filter) {      Impl::Instance().SetGlobalFilter(filter);  } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index eb629a33f..4b9a910c1 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -1,43 +1,32 @@  // Copyright 2014 Citra Emulator Project  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +  #pragma once -#include <chrono>  #include <filesystem>  #include <memory>  #include <string>  #include <string_view> -#include "common/fs/file.h"  #include "common/logging/filter.h"  #include "common/logging/log.h" +namespace Common::FS { +class IOFile; +} +  namespace Common::Log {  class Filter;  /** - * 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{}; -    const char* filename = nullptr; -    unsigned int line_num = 0; -    std::string function; -    std::string message; -    bool final_entry = false; -}; - -/**   * Interface for logging backends. As loggers can be created and removed at runtime, this can be   * used by a frontend for adding a custom logging backend as needed   */  class Backend {  public:      virtual ~Backend() = default; +      virtual void SetFilter(const Filter& new_filter) {          filter = new_filter;      } @@ -53,6 +42,8 @@ private:   */  class ConsoleBackend : public Backend {  public: +    ~ConsoleBackend() override; +      static const char* Name() {          return "console";      } @@ -67,6 +58,8 @@ public:   */  class ColorConsoleBackend : public Backend {  public: +    ~ColorConsoleBackend() override; +      static const char* Name() {          return "color_console";      } @@ -83,6 +76,7 @@ public:  class FileBackend : public Backend {  public:      explicit FileBackend(const std::filesystem::path& filename); +    ~FileBackend() override;      static const char* Name() {          return "file"; @@ -95,7 +89,7 @@ public:      void Write(const Entry& entry) override;  private: -    FS::IOFile file; +    std::unique_ptr<FS::IOFile> file;      std::size_t bytes_written = 0;  }; @@ -104,6 +98,8 @@ private:   */  class DebuggerBackend : public Backend {  public: +    ~DebuggerBackend() override; +      static const char* Name() {          return "debugger";      } @@ -120,17 +116,6 @@ void RemoveBackend(std::string_view backend_name);  Backend* GetBackend(std::string_view backend_name);  /** - * Returns the name of the passed log class as a C-string. Subclasses are separated by periods - * instead of underscores as in the enumeration. - */ -const char* GetLogClassName(Class log_class); - -/** - * Returns the name of the passed log level as a C-string. - */ -const char* GetLevelName(Level log_level); - -/**   * The global filter will prevent any messages from even being processed if they are filtered. Each   * backend can have a filter, but if the level is lower than the global filter, the backend will   * never get the message diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 20a2dd106..4f2cc29e1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -3,7 +3,6 @@  // Refer to the license.txt file included.  #include <algorithm> -#include "common/logging/backend.h"  #include "common/logging/filter.h"  #include "common/string_util.h" @@ -22,7 +21,7 @@ Level GetLevelByName(const It begin, const It end) {  template <typename It>  Class GetClassByName(const It begin, const It end) { -    for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { +    for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) {          const char* level_name = GetLogClassName(static_cast<Class>(i));          if (Common::ComparePartialString(begin, end, level_name)) {              return static_cast<Class>(i); @@ -62,6 +61,135 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {  }  } // Anonymous namespace +/// 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, ARM)                                                                                 \ +    SUB(Core, Timing)                                                                              \ +    CLS(Config)                                                                                    \ +    CLS(Debug)                                                                                     \ +    SUB(Debug, Emulated)                                                                           \ +    SUB(Debug, GPU)                                                                                \ +    SUB(Debug, Breakpoint)                                                                         \ +    SUB(Debug, GDBStub)                                                                            \ +    CLS(Kernel)                                                                                    \ +    SUB(Kernel, SVC)                                                                               \ +    CLS(Service)                                                                                   \ +    SUB(Service, ACC)                                                                              \ +    SUB(Service, Audio)                                                                            \ +    SUB(Service, AM)                                                                               \ +    SUB(Service, AOC)                                                                              \ +    SUB(Service, APM)                                                                              \ +    SUB(Service, ARP)                                                                              \ +    SUB(Service, BCAT)                                                                             \ +    SUB(Service, BPC)                                                                              \ +    SUB(Service, BGTC)                                                                             \ +    SUB(Service, BTDRV)                                                                            \ +    SUB(Service, BTM)                                                                              \ +    SUB(Service, Capture)                                                                          \ +    SUB(Service, ERPT)                                                                             \ +    SUB(Service, ETicket)                                                                          \ +    SUB(Service, EUPLD)                                                                            \ +    SUB(Service, Fatal)                                                                            \ +    SUB(Service, FGM)                                                                              \ +    SUB(Service, Friend)                                                                           \ +    SUB(Service, FS)                                                                               \ +    SUB(Service, GRC)                                                                              \ +    SUB(Service, HID)                                                                              \ +    SUB(Service, IRS)                                                                              \ +    SUB(Service, LBL)                                                                              \ +    SUB(Service, LDN)                                                                              \ +    SUB(Service, LDR)                                                                              \ +    SUB(Service, LM)                                                                               \ +    SUB(Service, Migration)                                                                        \ +    SUB(Service, Mii)                                                                              \ +    SUB(Service, MM)                                                                               \ +    SUB(Service, NCM)                                                                              \ +    SUB(Service, NFC)                                                                              \ +    SUB(Service, NFP)                                                                              \ +    SUB(Service, NIFM)                                                                             \ +    SUB(Service, NIM)                                                                              \ +    SUB(Service, NPNS)                                                                             \ +    SUB(Service, NS)                                                                               \ +    SUB(Service, NVDRV)                                                                            \ +    SUB(Service, OLSC)                                                                             \ +    SUB(Service, PCIE)                                                                             \ +    SUB(Service, PCTL)                                                                             \ +    SUB(Service, PCV)                                                                              \ +    SUB(Service, PM)                                                                               \ +    SUB(Service, PREPO)                                                                            \ +    SUB(Service, PSC)                                                                              \ +    SUB(Service, PSM)                                                                              \ +    SUB(Service, SET)                                                                              \ +    SUB(Service, SM)                                                                               \ +    SUB(Service, SPL)                                                                              \ +    SUB(Service, SSL)                                                                              \ +    SUB(Service, TCAP)                                                                             \ +    SUB(Service, Time)                                                                             \ +    SUB(Service, USB)                                                                              \ +    SUB(Service, VI)                                                                               \ +    SUB(Service, WLAN)                                                                             \ +    CLS(HW)                                                                                        \ +    SUB(HW, Memory)                                                                                \ +    SUB(HW, LCD)                                                                                   \ +    SUB(HW, GPU)                                                                                   \ +    SUB(HW, AES)                                                                                   \ +    CLS(IPC)                                                                                       \ +    CLS(Frontend)                                                                                  \ +    CLS(Render)                                                                                    \ +    SUB(Render, Software)                                                                          \ +    SUB(Render, OpenGL)                                                                            \ +    SUB(Render, Vulkan)                                                                            \ +    CLS(Audio)                                                                                     \ +    SUB(Audio, DSP)                                                                                \ +    SUB(Audio, Sink)                                                                               \ +    CLS(Input)                                                                                     \ +    CLS(Network)                                                                                   \ +    CLS(Loader)                                                                                    \ +    CLS(CheatEngine)                                                                               \ +    CLS(Crypto)                                                                                    \ +    CLS(WebService) + +// GetClassName is a macro defined by Windows.h, grrr... +const char* 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 +    case Class::Count: +        break; +    } +    return "Invalid"; +} + +const char* 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); +    case Level::Count: +        break; +    } +#undef LVL +    return "Invalid"; +} +  Filter::Filter(Level default_level) {      ResetAll(default_level);  } diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index f5673a9f6..1a3074e04 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -5,6 +5,7 @@  #pragma once  #include <array> +#include <chrono>  #include <cstddef>  #include <string_view>  #include "common/logging/log.h" @@ -12,6 +13,17 @@  namespace Common::Log {  /** + * Returns the name of the passed log class as a C-string. Subclasses are separated by periods + * instead of underscores as in the enumeration. + */ +const char* GetLogClassName(Class log_class); + +/** + * Returns the name of the passed log level as a C-string. + */ +const char* GetLevelName(Level log_level); + +/**   * 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. diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 1f0f8db52..8d43eddc7 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -5,7 +5,7 @@  #pragma once  #include <fmt/format.h> -#include "common/common_types.h" +#include "common/logging/types.h"  namespace Common::Log { @@ -18,124 +18,6 @@ constexpr const char* TrimSourcePath(std::string_view source) {      return source.data() + idx;  } -/// 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 threaten 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 - * backend.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_ARM,          ///< ARM CPU core -    Core_Timing,       ///< CoreTiming functions -    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 -    Debug_GDBStub,     ///< GDB Stub -    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_ACC,       ///< The ACC (Accounts) service -    Service_AM,        ///< The AM (Applet manager) service -    Service_AOC,       ///< The AOC (AddOn Content) service -    Service_APM,       ///< The APM (Performance) service -    Service_ARP,       ///< The ARP service -    Service_Audio,     ///< The Audio (Audio control) service -    Service_BCAT,      ///< The BCAT service -    Service_BGTC,      ///< The BGTC (Background Task Controller) service -    Service_BPC,       ///< The BPC service -    Service_BTDRV,     ///< The Bluetooth driver service -    Service_BTM,       ///< The BTM service -    Service_Capture,   ///< The capture service -    Service_ERPT,      ///< The error reporting service -    Service_ETicket,   ///< The ETicket service -    Service_EUPLD,     ///< The error upload service -    Service_Fatal,     ///< The Fatal service -    Service_FGM,       ///< The FGM service -    Service_Friend,    ///< The friend service -    Service_FS,        ///< The FS (Filesystem) service -    Service_GRC,       ///< The game recording service -    Service_HID,       ///< The HID (Human interface device) service -    Service_IRS,       ///< The IRS service -    Service_LBL,       ///< The LBL (LCD backlight) service -    Service_LDN,       ///< The LDN (Local domain network) service -    Service_LDR,       ///< The loader service -    Service_LM,        ///< The LM (Logger) service -    Service_Migration, ///< The migration service -    Service_Mii,       ///< The Mii service -    Service_MM,        ///< The MM (Multimedia) service -    Service_NCM,       ///< The NCM service -    Service_NFC,       ///< The NFC (Near-field communication) service -    Service_NFP,       ///< The NFP service -    Service_NIFM,      ///< The NIFM (Network interface) service -    Service_NIM,       ///< The NIM service -    Service_NPNS,      ///< The NPNS service -    Service_NS,        ///< The NS services -    Service_NVDRV,     ///< The NVDRV (Nvidia driver) service -    Service_OLSC,      ///< The OLSC service -    Service_PCIE,      ///< The PCIe service -    Service_PCTL,      ///< The PCTL (Parental control) service -    Service_PCV,       ///< The PCV service -    Service_PM,        ///< The PM service -    Service_PREPO,     ///< The PREPO (Play report) service -    Service_PSC,       ///< The PSC service -    Service_PSM,       ///< The PSM service -    Service_SET,       ///< The SET (Settings) service -    Service_SM,        ///< The SM (Service manager) service -    Service_SPL,       ///< The SPL service -    Service_SSL,       ///< The SSL service -    Service_TCAP,      ///< The TCAP service. -    Service_Time,      ///< The time service -    Service_USB,       ///< The USB (Universal Serial Bus) service -    Service_VI,        ///< The VI (Video interface) service -    Service_WLAN,      ///< The WLAN (Wireless local area network) service -    HW,                ///< Low-level hardware emulation -    HW_Memory,         ///< Memory-map and address translation -    HW_LCD,            ///< LCD register emulation -    HW_GPU,            ///< GPU control emulation -    HW_AES,            ///< AES engine emulation -    IPC,               ///< IPC interface -    Frontend,          ///< Emulator UI -    Render,            ///< Emulator video output and hardware acceleration -    Render_Software,   ///< Software renderer backend -    Render_OpenGL,     ///< OpenGL backend -    Render_Vulkan,     ///< Vulkan backend -    Audio,             ///< Audio emulation -    Audio_DSP,         ///< The HLE implementation of the DSP -    Audio_Sink,        ///< Emulator audio output backend -    Loader,            ///< ROM loader -    CheatEngine,       ///< Memory manipulation and engine VM functions -    Crypto,            ///< Cryptographic engine/functions -    Input,             ///< Input emulation -    Network,           ///< Network emulation -    WebService,        ///< Interface to yuzu Web Services -    Count              ///< Total number of logging classes -}; -  /// Logs a message to the global logger, using fmt  void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,                         unsigned int line_num, const char* function, const char* format, diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 80ee2cca1..cfc0d5846 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -11,7 +11,7 @@  #include "common/assert.h"  #include "common/common_funcs.h" -#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" diff --git a/src/common/logging/types.h b/src/common/logging/types.h new file mode 100644 index 000000000..88b0e9c01 --- /dev/null +++ b/src/common/logging/types.h @@ -0,0 +1,144 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> + +#include "common/common_types.h" + +namespace Common::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 threaten the stability of the entire +              ///< application. + +    Count ///< Total number of logging levels +}; + +/** + * 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 + * filter.cpp. + */ +enum class Class : u8 { +    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_ARM,          ///< ARM CPU core +    Core_Timing,       ///< CoreTiming functions +    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 +    Debug_GDBStub,     ///< GDB Stub +    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_ACC,       ///< The ACC (Accounts) service +    Service_AM,        ///< The AM (Applet manager) service +    Service_AOC,       ///< The AOC (AddOn Content) service +    Service_APM,       ///< The APM (Performance) service +    Service_ARP,       ///< The ARP service +    Service_Audio,     ///< The Audio (Audio control) service +    Service_BCAT,      ///< The BCAT service +    Service_BGTC,      ///< The BGTC (Background Task Controller) service +    Service_BPC,       ///< The BPC service +    Service_BTDRV,     ///< The Bluetooth driver service +    Service_BTM,       ///< The BTM service +    Service_Capture,   ///< The capture service +    Service_ERPT,      ///< The error reporting service +    Service_ETicket,   ///< The ETicket service +    Service_EUPLD,     ///< The error upload service +    Service_Fatal,     ///< The Fatal service +    Service_FGM,       ///< The FGM service +    Service_Friend,    ///< The friend service +    Service_FS,        ///< The FS (Filesystem) service +    Service_GRC,       ///< The game recording service +    Service_HID,       ///< The HID (Human interface device) service +    Service_IRS,       ///< The IRS service +    Service_LBL,       ///< The LBL (LCD backlight) service +    Service_LDN,       ///< The LDN (Local domain network) service +    Service_LDR,       ///< The loader service +    Service_LM,        ///< The LM (Logger) service +    Service_Migration, ///< The migration service +    Service_Mii,       ///< The Mii service +    Service_MM,        ///< The MM (Multimedia) service +    Service_NCM,       ///< The NCM service +    Service_NFC,       ///< The NFC (Near-field communication) service +    Service_NFP,       ///< The NFP service +    Service_NIFM,      ///< The NIFM (Network interface) service +    Service_NIM,       ///< The NIM service +    Service_NPNS,      ///< The NPNS service +    Service_NS,        ///< The NS services +    Service_NVDRV,     ///< The NVDRV (Nvidia driver) service +    Service_OLSC,      ///< The OLSC service +    Service_PCIE,      ///< The PCIe service +    Service_PCTL,      ///< The PCTL (Parental control) service +    Service_PCV,       ///< The PCV service +    Service_PM,        ///< The PM service +    Service_PREPO,     ///< The PREPO (Play report) service +    Service_PSC,       ///< The PSC service +    Service_PSM,       ///< The PSM service +    Service_SET,       ///< The SET (Settings) service +    Service_SM,        ///< The SM (Service manager) service +    Service_SPL,       ///< The SPL service +    Service_SSL,       ///< The SSL service +    Service_TCAP,      ///< The TCAP service. +    Service_Time,      ///< The time service +    Service_USB,       ///< The USB (Universal Serial Bus) service +    Service_VI,        ///< The VI (Video interface) service +    Service_WLAN,      ///< The WLAN (Wireless local area network) service +    HW,                ///< Low-level hardware emulation +    HW_Memory,         ///< Memory-map and address translation +    HW_LCD,            ///< LCD register emulation +    HW_GPU,            ///< GPU control emulation +    HW_AES,            ///< AES engine emulation +    IPC,               ///< IPC interface +    Frontend,          ///< Emulator UI +    Render,            ///< Emulator video output and hardware acceleration +    Render_Software,   ///< Software renderer backend +    Render_OpenGL,     ///< OpenGL backend +    Render_Vulkan,     ///< Vulkan backend +    Audio,             ///< Audio emulation +    Audio_DSP,         ///< The HLE implementation of the DSP +    Audio_Sink,        ///< Emulator audio output backend +    Loader,            ///< ROM loader +    CheatEngine,       ///< Memory manipulation and engine VM functions +    Crypto,            ///< Cryptographic engine/functions +    Input,             ///< Input emulation +    Network,           ///< Network emulation +    WebService,        ///< Interface to yuzu Web Services +    Count              ///< Total number of logging classes +}; + +/** + * 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{}; +    const char* filename = nullptr; +    unsigned int line_num = 0; +    std::string function; +    std::string message; +    bool final_entry = false; +}; + +} // namespace Common::Log diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 360e878d6..e1bb4b7ff 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -55,9 +55,11 @@ void LogSettings() {      log_setting("Renderer_UseAsynchronousGpuEmulation",                  values.use_asynchronous_gpu_emulation.GetValue());      log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); +    log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());      log_setting("Renderer_UseVsync", values.use_vsync.GetValue());      log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue());      log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); +    log_setting("Renderer_UseGarbageCollection", values.use_caches_gc.GetValue());      log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());      log_setting("Audio_OutputEngine", values.sink_id);      log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); @@ -121,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) {      values.cpu_accuracy.SetGlobal(true);      values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);      values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); +    values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);      values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);      values.cpuopt_unsafe_fastmem_check.SetGlobal(true); @@ -135,10 +138,12 @@ void RestoreGlobalState(bool is_powered_on) {      values.gpu_accuracy.SetGlobal(true);      values.use_asynchronous_gpu_emulation.SetGlobal(true);      values.use_nvdec_emulation.SetGlobal(true); +    values.accelerate_astc.SetGlobal(true);      values.use_vsync.SetGlobal(true);      values.use_assembly_shaders.SetGlobal(true);      values.use_asynchronous_shaders.SetGlobal(true);      values.use_fast_gpu_time.SetGlobal(true); +    values.use_caches_gc.SetGlobal(true);      values.bg_red.SetGlobal(true);      values.bg_green.SetGlobal(true);      values.bg_blue.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 1af8c5ac2..82ec18e27 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -129,6 +129,7 @@ struct Values {      Setting<bool> cpuopt_unsafe_unfuse_fma;      Setting<bool> cpuopt_unsafe_reduce_fp_error; +    Setting<bool> cpuopt_unsafe_ignore_standard_fpcr;      Setting<bool> cpuopt_unsafe_inaccurate_nan;      Setting<bool> cpuopt_unsafe_fastmem_check; @@ -147,10 +148,13 @@ struct Values {      Setting<GPUAccuracy> gpu_accuracy;      Setting<bool> use_asynchronous_gpu_emulation;      Setting<bool> use_nvdec_emulation; +    Setting<bool> accelerate_astc;      Setting<bool> use_vsync; +    Setting<bool> disable_fps_limit;      Setting<bool> use_assembly_shaders;      Setting<bool> use_asynchronous_shaders;      Setting<bool> use_fast_gpu_time; +    Setting<bool> use_caches_gc;      Setting<float> bg_red;      Setting<float> bg_green; @@ -218,6 +222,7 @@ struct Values {      std::string program_args;      bool dump_exefs;      bool dump_nso; +    bool enable_fs_access_log;      bool reporting_services;      bool quest_flag;      bool disable_macro_jit; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index efb851f5a..83b5b7676 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -139,6 +139,7 @@ add_library(core STATIC      frontend/input.h      hardware_interrupt_manager.cpp      hardware_interrupt_manager.h +    hle/api_version.h      hle/ipc.h      hle/ipc_helpers.h      hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -550,6 +551,8 @@ add_library(core STATIC      hle/service/spl/module.h      hle/service/spl/spl.cpp      hle/service/spl/spl.h +    hle/service/spl/spl_results.h +    hle/service/spl/spl_types.h      hle/service/ssl/ssl.cpp      hle/service/ssl/ssl.h      hle/service/time/clock_types.h diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c8f6dc765..77a44f862 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -8,6 +8,7 @@  #include <dynarmic/interface/A32/config.h>  #include <dynarmic/interface/A32/context.h>  #include "common/assert.h" +#include "common/literals.h"  #include "common/logging/log.h"  #include "common/page_table.h"  #include "common/settings.h" @@ -22,6 +23,8 @@  namespace Core { +using namespace Common::Literals; +  class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {  public:      explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) @@ -143,8 +146,8 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*      config.wall_clock_cntpct = uses_wall_clock;      // Code cache size -    config.code_cache_size = 512 * 1024 * 1024; -    config.far_code_offset = 400 * 1024 * 1024; +    config.code_cache_size = 512_MiB; +    config.far_code_offset = 400_MiB;      // Safe optimizations      if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { @@ -186,6 +189,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*          if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {              config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;          } +        if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()) { +            config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; +        }          if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {              config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;          } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index ba524cd05..75332e348 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -7,6 +7,7 @@  #include <dynarmic/interface/A64/a64.h>  #include <dynarmic/interface/A64/config.h>  #include "common/assert.h" +#include "common/literals.h"  #include "common/logging/log.h"  #include "common/page_table.h"  #include "common/settings.h" @@ -24,6 +25,7 @@  namespace Core {  using Vector = Dynarmic::A64::Vector; +using namespace Common::Literals;  class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {  public: @@ -184,8 +186,8 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*      config.wall_clock_cntpct = uses_wall_clock;      // Code cache size -    config.code_cache_size = 512 * 1024 * 1024; -    config.far_code_offset = 400 * 1024 * 1024; +    config.code_cache_size = 512_MiB; +    config.far_code_offset = 400_MiB;      // Safe optimizations      if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { diff --git a/src/core/core.cpp b/src/core/core.cpp index c5004b7b4..e6f1aa0e7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <array> +#include <atomic>  #include <memory>  #include <utility> @@ -377,7 +378,7 @@ struct System::Impl {      std::unique_ptr<Core::DeviceMemory> device_memory;      Core::Memory::Memory memory;      CpuManager cpu_manager; -    bool is_powered_on = false; +    std::atomic_bool is_powered_on{};      bool exit_lock = false;      Reporter reporter; @@ -463,7 +464,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st  }  bool System::IsPoweredOn() const { -    return impl->is_powered_on; +    return impl->is_powered_on.load(std::memory_order::relaxed);  }  void System::PrepareReschedule() { diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index fb451a423..a98daed89 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -835,7 +835,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {              "key_area_key_ocean_{:02X}",              "key_area_key_system_{:02X}",          }; -        WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key); +        WriteKeyToFile(category, fmt::format(fmt::runtime(kak_names.at(field2)), field1), key);      } else if (id == S128KeyType::Master) {          WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);      } else if (id == S128KeyType::Package1) { diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 53b8b7ca0..7c0950bb0 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -345,8 +345,10 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(  static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,                             const Service::FileSystem::FileSystemController& fs_controller) {      const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); +    const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);      if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || -        load_dir == nullptr || load_dir->GetSize() <= 0) { +        ((load_dir == nullptr || load_dir->GetSize() <= 0) && +         (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {          return;      } @@ -356,7 +358,10 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t      }      const auto& disabled = Settings::values.disabled_addons[title_id]; -    auto patch_dirs = load_dir->GetSubdirectories(); +    std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); +    if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { +        patch_dirs.push_back(sdmc_load_dir); +    }      std::sort(patch_dirs.begin(), patch_dirs.end(),                [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -402,7 +407,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t  }  VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, -                                     VirtualFile update_raw) const { +                                     VirtualFile update_raw, bool apply_layeredfs) const {      const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",                                          title_id, static_cast<u8>(type)); @@ -442,7 +447,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content      }      // LayeredFS -    ApplyLayeredFS(romfs, title_id, type, fs_controller); +    if (apply_layeredfs) { +        ApplyLayeredFS(romfs, title_id, type, fs_controller); +    }      return romfs;  } @@ -524,6 +531,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u          }      } +    // SDMC mod directory (RomFS LayeredFS) +    const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); +    if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 && +        IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) { +        const auto mod_disabled = +            std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); +        out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS"); +    } +      // DLC      const auto dlc_entries =          content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index fb1853035..3be871f35 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -64,7 +64,8 @@ public:      // - LayeredFS      [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,                                           ContentRecordType type = ContentRecordType::Program, -                                         VirtualFile update_raw = nullptr) const; +                                         VirtualFile update_raw = nullptr, +                                         bool apply_layeredfs = true) const;      // Returns a vector of pairs between patch names and patch versions.      // i.e. Update 3.2.2 will return {"Update", "3.2.2"} diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 066c6789a..7a646b5f1 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -58,14 +58,17 @@ static bool FollowsNcaIdFormat(std::string_view name) {  static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,                                              bool within_two_digit, bool cnmt_suffix) { -    if (!within_two_digit) -        return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca", -                           Common::HexToString(nca_id, second_hex_upper)); +    if (!within_two_digit) { +        const auto format_str = fmt::runtime(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca"); +        return fmt::format(format_str, Common::HexToString(nca_id, second_hex_upper)); +    }      Core::Crypto::SHA256Hash hash{};      mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0); -    return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0], -                       Common::HexToString(nca_id, second_hex_upper)); + +    const auto format_str = +        fmt::runtime(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca"); +    return fmt::format(format_str, hash[0], Common::HexToString(nca_id, second_hex_upper));  }  static std::string GetCNMTName(TitleType type, u64 title_id) { diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index cb56d8f2d..e5c72cd4d 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -12,23 +12,32 @@ namespace FileSys {  constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB -SDMCFactory::SDMCFactory(VirtualDir dir_) -    : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>( -                                GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), -                                [](const VirtualFile& file, const NcaID& id) { -                                    return NAX{file, id}.GetDecrypted(); -                                })), +SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_) +    : sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)), +      contents(std::make_unique<RegisteredCache>( +          GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"), +          [](const VirtualFile& file, const NcaID& id) { +              return NAX{file, id}.GetDecrypted(); +          })),        placeholder(std::make_unique<PlaceholderCache>( -          GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {} +          GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {}  SDMCFactory::~SDMCFactory() = default;  ResultVal<VirtualDir> SDMCFactory::Open() const { -    return MakeResult<VirtualDir>(dir); +    return MakeResult<VirtualDir>(sd_dir); +} + +VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const { +    // LayeredFS doesn't work on updates and title id-less homebrew +    if (title_id == 0 || (title_id & 0xFFF) == 0x800) { +        return nullptr; +    } +    return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id));  }  VirtualDir SDMCFactory::GetSDMCContentDirectory() const { -    return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents"); +    return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents");  }  RegisteredCache* SDMCFactory::GetSDMCContents() const { @@ -40,11 +49,11 @@ PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {  }  VirtualDir SDMCFactory::GetImageDirectory() const { -    return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album"); +    return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album");  }  u64 SDMCFactory::GetSDMCFreeSpace() const { -    return GetSDMCTotalSpace() - dir->GetSize(); +    return GetSDMCTotalSpace() - sd_dir->GetSize();  }  u64 SDMCFactory::GetSDMCTotalSpace() const { diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 2bb92ba93..3a3d11f3a 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -16,11 +16,12 @@ class PlaceholderCache;  /// File system interface to the SDCard archive  class SDMCFactory {  public: -    explicit SDMCFactory(VirtualDir dir); +    explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);      ~SDMCFactory();      ResultVal<VirtualDir> Open() const; +    VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;      VirtualDir GetSDMCContentDirectory() const;      RegisteredCache* GetSDMCContents() const; @@ -32,7 +33,8 @@ public:      u64 GetSDMCTotalSpace() const;  private: -    VirtualDir dir; +    VirtualDir sd_dir; +    VirtualDir sd_mod_dir;      std::unique_ptr<RegisteredCache> contents;      std::unique_ptr<PlaceholderCache> placeholder; diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index 54704105b..9b76d007e 100644 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -4,47 +4,29 @@  #include "core/file_sys/system_archive/system_version.h"  #include "core/file_sys/vfs_vector.h" +#include "core/hle/api_version.h"  namespace FileSys::SystemArchive { -namespace SystemVersionData { - -// This section should reflect the best system version to describe yuzu's HLE api. -// TODO(DarkLordZach): Update when HLE gets better. - -constexpr u8 VERSION_MAJOR = 11; -constexpr u8 VERSION_MINOR = 0; -constexpr u8 VERSION_MICRO = 1; - -constexpr u8 REVISION_MAJOR = 1; -constexpr u8 REVISION_MINOR = 0; - -constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf"; -constexpr char DISPLAY_VERSION[] = "11.0.1"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0"; - -} // namespace SystemVersionData -  std::string GetLongDisplayVersion() { -    return SystemVersionData::DISPLAY_TITLE; +    return HLE::ApiVersion::DISPLAY_TITLE;  }  VirtualDir SystemVersion() {      VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file"); -    file->WriteObject(SystemVersionData::VERSION_MAJOR, 0); -    file->WriteObject(SystemVersionData::VERSION_MINOR, 1); -    file->WriteObject(SystemVersionData::VERSION_MICRO, 2); -    file->WriteObject(SystemVersionData::REVISION_MAJOR, 4); -    file->WriteObject(SystemVersionData::REVISION_MINOR, 5); -    file->WriteArray(SystemVersionData::PLATFORM_STRING, -                     std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8); -    file->WriteArray(SystemVersionData::VERSION_HASH, -                     std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28); -    file->WriteArray(SystemVersionData::DISPLAY_VERSION, -                     std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68); -    file->WriteArray(SystemVersionData::DISPLAY_TITLE, -                     std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80); +    file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0); +    file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1); +    file->WriteObject(HLE::ApiVersion::HOS_VERSION_MICRO, 2); +    file->WriteObject(HLE::ApiVersion::SDK_REVISION_MAJOR, 4); +    file->WriteObject(HLE::ApiVersion::SDK_REVISION_MINOR, 5); +    file->WriteArray(HLE::ApiVersion::PLATFORM_STRING, +                     std::min<u64>(sizeof(HLE::ApiVersion::PLATFORM_STRING), 0x20ULL), 0x8); +    file->WriteArray(HLE::ApiVersion::VERSION_HASH, +                     std::min<u64>(sizeof(HLE::ApiVersion::VERSION_HASH), 0x40ULL), 0x28); +    file->WriteArray(HLE::ApiVersion::DISPLAY_VERSION, +                     std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_VERSION), 0x18ULL), 0x68); +    file->WriteArray(HLE::ApiVersion::DISPLAY_TITLE, +                     std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_TITLE), 0x80ULL), 0x80);      return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},                                                  std::vector<VirtualDir>{}, "data");  } diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 215e1cb1a..368419eca 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -6,7 +6,6 @@  #include <numeric>  #include <string>  #include "common/fs/path_util.h" -#include "common/logging/backend.h"  #include "core/file_sys/mode.h"  #include "core/file_sys/vfs.h" diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp index cd162c0c3..00e256779 100644 --- a/src/core/file_sys/vfs_libzip.cpp +++ b/src/core/file_sys/vfs_libzip.cpp @@ -14,7 +14,6 @@  #endif  #include "common/fs/path_util.h" -#include "common/logging/backend.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_libzip.h"  #include "core/file_sys/vfs_vector.h" diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index d0b8fd046..3dad54f49 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -24,17 +24,12 @@ constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {      case Mode::Read:          return FS::FileAccessMode::Read;      case Mode::Write: -        return FS::FileAccessMode::Write;      case Mode::ReadWrite: -        return FS::FileAccessMode::ReadWrite;      case Mode::Append: -        return FS::FileAccessMode::Append;      case Mode::ReadAppend: -        return FS::FileAccessMode::ReadAppend;      case Mode::WriteAppend: -        return FS::FileAccessMode::Append;      case Mode::All: -        return FS::FileAccessMode::ReadAppend; +        return FS::FileAccessMode::ReadWrite;      default:          return {};      } diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 7a047803e..f1747c5b2 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -4,6 +4,7 @@  #pragma once +#include <functional>  #include <memory>  #include <string>  #include <tuple> diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h new file mode 100644 index 000000000..5e10a7ad9 --- /dev/null +++ b/src/core/hle/api_version.h @@ -0,0 +1,40 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +// This file contains yuzu's HLE API version constants. + +namespace HLE::ApiVersion { + +// Horizon OS version constants. + +constexpr u8 HOS_VERSION_MAJOR = 11; +constexpr u8 HOS_VERSION_MINOR = 0; +constexpr u8 HOS_VERSION_MICRO = 1; + +// NintendoSDK version constants. + +constexpr u8 SDK_REVISION_MAJOR = 1; +constexpr u8 SDK_REVISION_MINOR = 0; + +constexpr char PLATFORM_STRING[] = "NX"; +constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf"; +constexpr char DISPLAY_VERSION[] = "11.0.1"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0"; + +// Atmosphere version constants. + +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 4; + +constexpr u32 GetTargetFirmware() { +    return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 | +           u32{HOS_VERSION_MICRO} << 8 | 0U; +} + +} // namespace HLE::ApiVersion diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 61bda3786..ceff2532d 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -345,8 +345,12 @@ public:      explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {}      explicit RequestParser(Kernel::HLERequestContext& ctx) : RequestHelperBase(ctx) { -        ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); -        Skip(ctx.GetDataPayloadOffset(), false); +        // TIPC does not have data payload offset +        if (!ctx.IsTipc()) { +            ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); +            Skip(ctx.GetDataPayloadOffset(), false); +        } +          // Skip the u64 command id, it's already stored in the context          static constexpr u32 CommandIdSize = 2;          Skip(CommandIdSize, false); diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp index 86472b5ce..6f335c251 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -4,7 +4,8 @@  #include <random> -#include "common/common_sizes.h" +#include "common/literals.h" +  #include "core/hle/kernel/board/nintendo/nx/k_system_control.h"  #include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"  #include "core/hle/kernel/k_trace.h" @@ -25,6 +26,8 @@ constexpr const std::size_t RequiredNonSecureSystemMemorySize =  namespace { +using namespace Common::Literals; +  u32 GetMemoryModeForInit() {      return 0x01;  } @@ -57,11 +60,11 @@ size_t KSystemControl::Init::GetIntendedMemorySize() {      switch (GetMemorySizeForInit()) {      case Smc::MemorySize_4GB:      default: // All invalid modes should go to 4GB. -        return Common::Size_4_GB; +        return 4_GiB;      case Smc::MemorySize_6GB: -        return Common::Size_6_GB; +        return 6_GiB;      case Smc::MemorySize_8GB: -        return Common::Size_8_GB; +        return 8_GiB;      }  } @@ -79,17 +82,17 @@ std::size_t KSystemControl::Init::GetApplicationPoolSize() {          switch (GetMemoryArrangeForInit()) {          case Smc::MemoryArrangement_4GB:          default: -            return Common::Size_3285_MB; +            return 3285_MiB;          case Smc::MemoryArrangement_4GBForAppletDev: -            return Common::Size_2048_MB; +            return 2048_MiB;          case Smc::MemoryArrangement_4GBForSystemDev: -            return Common::Size_3285_MB; +            return 3285_MiB;          case Smc::MemoryArrangement_6GB: -            return Common::Size_4916_MB; +            return 4916_MiB;          case Smc::MemoryArrangement_6GBForAppletDev: -            return Common::Size_3285_MB; +            return 3285_MiB;          case Smc::MemoryArrangement_8GB: -            return Common::Size_4916_MB; +            return 4916_MiB;          }      }(); @@ -103,22 +106,22 @@ size_t KSystemControl::Init::GetAppletPoolSize() {          switch (GetMemoryArrangeForInit()) {          case Smc::MemoryArrangement_4GB:          default: -            return Common::Size_507_MB; +            return 507_MiB;          case Smc::MemoryArrangement_4GBForAppletDev: -            return Common::Size_1554_MB; +            return 1554_MiB;          case Smc::MemoryArrangement_4GBForSystemDev: -            return Common::Size_448_MB; +            return 448_MiB;          case Smc::MemoryArrangement_6GB: -            return Common::Size_562_MB; +            return 562_MiB;          case Smc::MemoryArrangement_6GBForAppletDev: -            return Common::Size_2193_MB; +            return 2193_MiB;          case Smc::MemoryArrangement_8GB: -            return Common::Size_2193_MB; +            return 2193_MiB;          }      }();      // Return (possibly) adjusted size. -    constexpr size_t ExtraSystemMemoryForAtmosphere = Common::Size_33_MB; +    constexpr size_t ExtraSystemMemoryForAtmosphere = 33_MiB;      return base_pool_size - ExtraSystemMemoryForAtmosphere - KTraceBufferSize;  } diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp index c7549f7a2..ca29edc88 100644 --- a/src/core/hle/kernel/k_address_space_info.cpp +++ b/src/core/hle/kernel/k_address_space_info.cpp @@ -5,34 +5,37 @@  #include <array>  #include "common/assert.h" -#include "common/common_sizes.h" +#include "common/literals.h"  #include "core/hle/kernel/k_address_space_info.h"  namespace Kernel {  namespace { +using namespace Common::Literals; + +constexpr u64 Size_Invalid = UINT64_MAX; +  // clang-format off  constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{ -   { .bit_width = 32, .address = Common::Size_2_MB   , .size = Common::Size_1_GB   - Common::Size_2_MB  , .type = KAddressSpaceInfo::Type::MapSmall, }, -   { .bit_width = 32, .address = Common::Size_1_GB   , .size = Common::Size_4_GB   - Common::Size_1_GB  , .type = KAddressSpaceInfo::Type::MapLarge, }, -   { .bit_width = 32, .address = Common::Size_Invalid, .size = Common::Size_1_GB                        , .type = KAddressSpaceInfo::Type::Alias,    }, -   { .bit_width = 32, .address = Common::Size_Invalid, .size = Common::Size_1_GB                        , .type = KAddressSpaceInfo::Type::Heap,     }, -   { .bit_width = 36, .address = Common::Size_128_MB , .size = Common::Size_2_GB   - Common::Size_128_MB, .type = KAddressSpaceInfo::Type::MapSmall, }, -   { .bit_width = 36, .address = Common::Size_2_GB   , .size = Common::Size_64_GB  - Common::Size_2_GB  , .type = KAddressSpaceInfo::Type::MapLarge, }, -   { .bit_width = 36, .address = Common::Size_Invalid, .size = Common::Size_6_GB                        , .type = KAddressSpaceInfo::Type::Heap,     }, -   { .bit_width = 36, .address = Common::Size_Invalid, .size = Common::Size_6_GB                        , .type = KAddressSpaceInfo::Type::Alias,    }, -   { .bit_width = 39, .address = Common::Size_128_MB , .size = Common::Size_512_GB - Common::Size_128_MB, .type = KAddressSpaceInfo::Type::Map39Bit, }, -   { .bit_width = 39, .address = Common::Size_Invalid, .size = Common::Size_64_GB                       , .type = KAddressSpaceInfo::Type::MapSmall  }, -   { .bit_width = 39, .address = Common::Size_Invalid, .size = Common::Size_6_GB                        , .type = KAddressSpaceInfo::Type::Heap,     }, -   { .bit_width = 39, .address = Common::Size_Invalid, .size = Common::Size_64_GB                       , .type = KAddressSpaceInfo::Type::Alias,    }, -   { .bit_width = 39, .address = Common::Size_Invalid, .size = Common::Size_2_GB                        , .type = KAddressSpaceInfo::Type::Stack,    }, +   { .bit_width = 32, .address = 2_MiB       , .size = 1_GiB   - 2_MiB  , .type = KAddressSpaceInfo::Type::MapSmall, }, +   { .bit_width = 32, .address = 1_GiB       , .size = 4_GiB   - 1_GiB  , .type = KAddressSpaceInfo::Type::MapLarge, }, +   { .bit_width = 32, .address = Size_Invalid, .size = 1_GiB            , .type = KAddressSpaceInfo::Type::Alias,    }, +   { .bit_width = 32, .address = Size_Invalid, .size = 1_GiB            , .type = KAddressSpaceInfo::Type::Heap,     }, +   { .bit_width = 36, .address = 128_MiB     , .size = 2_GiB   - 128_MiB, .type = KAddressSpaceInfo::Type::MapSmall, }, +   { .bit_width = 36, .address = 2_GiB       , .size = 64_GiB  - 2_GiB  , .type = KAddressSpaceInfo::Type::MapLarge, }, +   { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB            , .type = KAddressSpaceInfo::Type::Heap,     }, +   { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB            , .type = KAddressSpaceInfo::Type::Alias,    }, +   { .bit_width = 39, .address = 128_MiB     , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, +   { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB           , .type = KAddressSpaceInfo::Type::MapSmall  }, +   { .bit_width = 39, .address = Size_Invalid, .size = 6_GiB            , .type = KAddressSpaceInfo::Type::Heap,     }, +   { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB           , .type = KAddressSpaceInfo::Type::Alias,    }, +   { .bit_width = 39, .address = Size_Invalid, .size = 2_GiB            , .type = KAddressSpaceInfo::Type::Stack,    },  }};  // clang-format on  constexpr bool IsAllowedIndexForAddress(std::size_t index) { -    return index < AddressSpaceInfos.size() && -           AddressSpaceInfos[index].address != Common::Size_Invalid; +    return index < AddressSpaceInfos.size() && AddressSpaceInfos[index].address != Size_Invalid;  }  using IndexArray = diff --git a/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp b/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp index a78551291..af652af58 100644 --- a/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp +++ b/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include "common/alignment.h" +#include "common/literals.h"  #include "core/hle/kernel/k_memory_layout.h"  #include "core/hle/kernel/k_memory_manager.h"  #include "core/hle/kernel/k_system_control.h" @@ -12,8 +13,10 @@ namespace Kernel {  namespace { +using namespace Common::Literals; +  constexpr size_t CarveoutAlignment = 0x20000; -constexpr size_t CarveoutSizeMax = (512ULL * 1024 * 1024) - CarveoutAlignment; +constexpr size_t CarveoutSizeMax = (512_MiB) - CarveoutAlignment;  bool SetupPowerManagementControllerMemoryRegion(KMemoryLayout& memory_layout) {      // Above firmware 2.0.0, the PMC is not mappable. diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h index 288642d9a..57ff538cc 100644 --- a/src/core/hle/kernel/k_memory_layout.h +++ b/src/core/hle/kernel/k_memory_layout.h @@ -7,8 +7,7 @@  #include <utility>  #include "common/alignment.h" -#include "common/common_sizes.h" -#include "common/common_types.h" +#include "common/literals.h"  #include "core/device_memory.h"  #include "core/hle/kernel/k_memory_region.h"  #include "core/hle/kernel/k_memory_region_type.h" @@ -16,20 +15,22 @@  namespace Kernel { -constexpr std::size_t L1BlockSize = Common::Size_1_GB; -constexpr std::size_t L2BlockSize = Common::Size_2_MB; +using namespace Common::Literals; + +constexpr std::size_t L1BlockSize = 1_GiB; +constexpr std::size_t L2BlockSize = 2_MiB;  constexpr std::size_t GetMaximumOverheadSize(std::size_t size) {      return (Common::DivideUp(size, L1BlockSize) + Common::DivideUp(size, L2BlockSize)) * PageSize;  } -constexpr std::size_t MainMemorySize = Common::Size_4_GB; -constexpr std::size_t MainMemorySizeMax = Common::Size_8_GB; +constexpr std::size_t MainMemorySize = 4_GiB; +constexpr std::size_t MainMemorySizeMax = 8_GiB; -constexpr std::size_t ReservedEarlyDramSize = 0x60000; +constexpr std::size_t ReservedEarlyDramSize = 384_KiB;  constexpr std::size_t DramPhysicalAddress = 0x80000000; -constexpr std::size_t KernelAslrAlignment = Common::Size_2_MB; +constexpr std::size_t KernelAslrAlignment = 2_MiB;  constexpr std::size_t KernelVirtualAddressSpaceWidth = 1ULL << 39;  constexpr std::size_t KernelPhysicalAddressSpaceWidth = 1ULL << 48; @@ -40,7 +41,7 @@ constexpr std::size_t KernelVirtualAddressSpaceLast = KernelVirtualAddressSpaceE  constexpr std::size_t KernelVirtualAddressSpaceSize =      KernelVirtualAddressSpaceEnd - KernelVirtualAddressSpaceBase;  constexpr std::size_t KernelVirtualAddressCodeBase = KernelVirtualAddressSpaceBase; -constexpr std::size_t KernelVirtualAddressCodeSize = 0x62000; +constexpr std::size_t KernelVirtualAddressCodeSize = 392_KiB;  constexpr std::size_t KernelVirtualAddressCodeEnd =      KernelVirtualAddressCodeBase + KernelVirtualAddressCodeSize; @@ -53,14 +54,14 @@ constexpr std::size_t KernelPhysicalAddressSpaceSize =  constexpr std::size_t KernelPhysicalAddressCodeBase = DramPhysicalAddress + ReservedEarlyDramSize;  constexpr std::size_t KernelPageTableHeapSize = GetMaximumOverheadSize(MainMemorySizeMax); -constexpr std::size_t KernelInitialPageHeapSize = Common::Size_128_KB; +constexpr std::size_t KernelInitialPageHeapSize = 128_KiB; -constexpr std::size_t KernelSlabHeapDataSize = Common::Size_5_MB; -constexpr std::size_t KernelSlabHeapGapsSize = Common::Size_2_MB - Common::Size_64_KB; +constexpr std::size_t KernelSlabHeapDataSize = 5_MiB; +constexpr std::size_t KernelSlabHeapGapsSize = 2_MiB - 64_KiB;  constexpr std::size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSize;  // NOTE: This is calculated from KThread slab counts, assuming KThread size <= 0x860. -constexpr std::size_t KernelSlabHeapAdditionalSize = 0x68000ULL; +constexpr std::size_t KernelSlabHeapAdditionalSize = 416_KiB;  constexpr std::size_t KernelResourceSize =      KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize; diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 66d260635..701268545 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -4,6 +4,7 @@  #include "common/alignment.h"  #include "common/assert.h" +#include "common/literals.h"  #include "common/scope_exit.h"  #include "core/core.h"  #include "core/hle/kernel/k_address_space_info.h" @@ -23,6 +24,8 @@ namespace Kernel {  namespace { +using namespace Common::Literals; +  constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {      switch (as_type) {      case FileSys::ProgramAddressSpaceType::Is32Bit: @@ -89,7 +92,7 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_      }      // Set code regions and determine remaining -    constexpr std::size_t RegionAlignment{2 * 1024 * 1024}; +    constexpr std::size_t RegionAlignment{2_MiB};      VAddr process_code_start{};      VAddr process_code_end{};      std::size_t stack_region_size{}; diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index da88f35bc..0c4bba66b 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -79,6 +79,7 @@ ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {      R_UNLESS(current_values[index] <= value, ResultInvalidState);      limit_values[index] = value; +    peak_values[index] = current_values[index];      return ResultSuccess;  } diff --git a/src/core/hle/kernel/k_trace.h b/src/core/hle/kernel/k_trace.h index 91ebf9ab2..79391bccb 100644 --- a/src/core/hle/kernel/k_trace.h +++ b/src/core/hle/kernel/k_trace.h @@ -4,9 +4,13 @@  #pragma once +#include "common/common_funcs.h" +  namespace Kernel { +using namespace Common::Literals; +  constexpr bool IsKTraceEnabled = false; -constexpr std::size_t KTraceBufferSize = IsKTraceEnabled ? 16 * 1024 * 1024 : 0; +constexpr std::size_t KTraceBufferSize = IsKTraceEnabled ? 16_MiB : 0;  } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 2ceeaeb5f..64bd0c494 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -12,7 +12,6 @@  #include <utility>  #include "common/assert.h" -#include "common/common_sizes.h"  #include "common/logging/log.h"  #include "common/microprofile.h"  #include "common/thread.h" @@ -180,7 +179,7 @@ struct KernelCore::Impl {          system_resource_limit->Reserve(LimitableResource::PhysicalMemory, kernel_size);          // Reserve secure applet memory, introduced in firmware 5.0.0 -        constexpr u64 secure_applet_memory_size{Common::Size_4_MB}; +        constexpr u64 secure_applet_memory_size{4_MiB};          ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,                                                secure_applet_memory_size)); @@ -320,8 +319,8 @@ struct KernelCore::Impl {          const VAddr code_end_virt_addr = KernelVirtualAddressCodeEnd;          // Setup the containing kernel region. -        constexpr size_t KernelRegionSize = Common::Size_1_GB; -        constexpr size_t KernelRegionAlign = Common::Size_1_GB; +        constexpr size_t KernelRegionSize = 1_GiB; +        constexpr size_t KernelRegionAlign = 1_GiB;          constexpr VAddr kernel_region_start =              Common::AlignDown(code_start_virt_addr, KernelRegionAlign);          size_t kernel_region_size = KernelRegionSize; @@ -368,7 +367,7 @@ struct KernelCore::Impl {          // Decide on the actual size for the misc region.          constexpr size_t MiscRegionAlign = KernelAslrAlignment; -        constexpr size_t MiscRegionMinimumSize = Common::Size_32_MB; +        constexpr size_t MiscRegionMinimumSize = 32_MiB;          const size_t misc_region_size = Common::AlignUp(              std::max(misc_region_needed_size, MiscRegionMinimumSize), MiscRegionAlign);          ASSERT(misc_region_size > 0); @@ -381,7 +380,7 @@ struct KernelCore::Impl {              misc_region_start, misc_region_size, KMemoryRegionType_KernelMisc));          // Setup the stack region. -        constexpr size_t StackRegionSize = Common::Size_14_MB; +        constexpr size_t StackRegionSize = 14_MiB;          constexpr size_t StackRegionAlign = KernelAslrAlignment;          const VAddr stack_region_start =              memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion( @@ -414,7 +413,7 @@ struct KernelCore::Impl {              slab_region_start, slab_region_size, KMemoryRegionType_KernelSlab));          // Setup the temp region. -        constexpr size_t TempRegionSize = Common::Size_128_MB; +        constexpr size_t TempRegionSize = 128_MiB;          constexpr size_t TempRegionAlign = KernelAslrAlignment;          const VAddr temp_region_start =              memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion( @@ -470,7 +469,7 @@ struct KernelCore::Impl {          // Determine size available for kernel page table heaps, requiring > 8 MB.          const PAddr resource_end_phys_addr = slab_start_phys_addr + resource_region_size;          const size_t page_table_heap_size = resource_end_phys_addr - slab_end_phys_addr; -        ASSERT(page_table_heap_size / Common::Size_4_MB > 2); +        ASSERT(page_table_heap_size / 4_MiB > 2);          // Insert a physical region for the kernel page table heap region          ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert( @@ -495,7 +494,7 @@ struct KernelCore::Impl {          ASSERT(linear_extents.GetEndAddress() != 0);          // Setup the linear mapping region. -        constexpr size_t LinearRegionAlign = Common::Size_1_GB; +        constexpr size_t LinearRegionAlign = 1_GiB;          const PAddr aligned_linear_phys_start =              Common::AlignDown(linear_extents.GetAddress(), LinearRegionAlign);          const size_t linear_region_size = diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index fec704c65..dd945e058 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -117,7 +117,7 @@ AOC_U::AOC_U(Core::System& system_)          {7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},          {8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},          {9, nullptr, "GetAddOnContentLostErrorCode"}, -        {10, nullptr, "GetAddOnContentListChangedEventWithProcessId"}, +        {10, &AOC_U::GetAddOnContentListChangedEventWithProcessId, "GetAddOnContentListChangedEventWithProcessId"},          {100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},          {101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},          {110, nullptr, "CreateContentsServiceManager"}, @@ -257,6 +257,14 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {      rb.PushCopyObjects(aoc_change_event.GetReadableEvent());  } +void AOC_U::GetAddOnContentListChangedEventWithProcessId(Kernel::HLERequestContext& ctx) { +    LOG_WARNING(Service_AOC, "(STUBBED) called"); + +    IPC::ResponseBuilder rb{ctx, 2, 1}; +    rb.Push(ResultSuccess); +    rb.PushCopyObjects(aoc_change_event.GetReadableEvent()); +} +  void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {      LOG_WARNING(Service_AOC, "(STUBBED) called"); diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 65095baa2..bb6ffb8eb 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -28,6 +28,7 @@ private:      void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);      void PrepareAddOnContent(Kernel::HLERequestContext& ctx);      void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx); +    void GetAddOnContentListChangedEventWithProcessId(Kernel::HLERequestContext& ctx);      void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);      void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 804c6b10c..92d4510b1 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -58,7 +58,7 @@ public:              {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},              {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},              {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, -            {10, nullptr, "GetAudioOutPlayedSampleCount"}, +            {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"},              {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"},              {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},              {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"}, @@ -186,6 +186,14 @@ private:          rb.Push(static_cast<u32>(stream->GetQueueSize()));      } +    void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { +        LOG_DEBUG(Service_Audio, "called"); + +        IPC::ResponseBuilder rb{ctx, 4}; +        rb.Push(ResultSuccess); +        rb.Push(stream->GetPlayedSampleCount()); +    } +      void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) {          LOG_DEBUG(Service_Audio, "called"); diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 10e6f7a64..33a6dbbb6 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -253,7 +253,11 @@ void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {      rb.Push<u32>(worker_buffer_sz);  } -void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) { +    GetWorkBufferSize(ctx); +} + +void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      const auto sample_rate = rp.Pop<u32>();      const auto channel_count = rp.Pop<u32>(); @@ -291,14 +295,47 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {          system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});  } +void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp{ctx}; +    const auto sample_rate = rp.Pop<u32>(); +    const auto channel_count = rp.Pop<u32>(); + +    LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + +    ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || +                   sample_rate == 12000 || sample_rate == 8000, +               "Invalid sample rate"); +    ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + +    const int num_stereo_streams = channel_count == 2 ? 1 : 0; +    const auto mapping_table = CreateMappingTable(channel_count); + +    int error = 0; +    OpusDecoderPtr decoder{ +        opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, +                                        num_stereo_streams, mapping_table.data(), &error)}; +    if (error != OPUS_OK || decoder == nullptr) { +        LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); +        IPC::ResponseBuilder rb{ctx, 2}; +        // TODO(ogniK): Use correct error code +        rb.Push(ResultUnknown); +        return; +    } + +    IPC::ResponseBuilder rb{ctx, 2, 0, 1}; +    rb.Push(ResultSuccess); +    rb.PushIpcInterface<IHardwareOpusDecoderManager>( +        system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); +} +  HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {      static const FunctionInfo functions[] = { -        {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"}, +        {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},          {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},          {2, nullptr, "OpenOpusDecoderForMultiStream"},          {3, nullptr, "GetWorkBufferSizeForMultiStream"}, -        {4, nullptr, "OpenHardwareOpusDecoderEx"}, -        {5, nullptr, "GetWorkBufferSizeEx"}, +        {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, +        {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},          {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},          {7, nullptr, "GetWorkBufferSizeForMultiStreamEx"},      }; diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 4f921f18e..b74824ff3 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -18,8 +18,10 @@ public:      ~HwOpus() override;  private: -    void OpenOpusDecoder(Kernel::HLERequestContext& ctx); +    void OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx); +    void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);      void GetWorkBufferSize(Kernel::HLERequestContext& ctx); +    void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);  };  } // namespace Service::Audio diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index d9fdc2dca..dc15cf58b 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -19,7 +19,6 @@  #include "common/fs/fs.h"  #include "common/fs/path_util.h"  #include "common/hex_util.h" -#include "common/logging/backend.h"  #include "common/logging/log.h"  #include "common/settings.h"  #include "core/core.h" @@ -314,7 +313,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe          LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);          if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { -            void(Common::FS::RemoveFile(zip_path)); +            Common::FS::RemoveFile(zip_path);          }          HandleDownloadDisplayResult(applet_manager, res); @@ -446,7 +445,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)              LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);              if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { -                void(Common::FS::RemoveFile(bin_file_path)); +                Common::FS::RemoveFile(bin_file_path);              }              HandleDownloadDisplayResult(applet_manager, res); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 3c16fe6c7..4a9b13e45 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -703,6 +703,16 @@ FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id)      return bis_factory->GetModificationLoadRoot(title_id);  } +FileSys::VirtualDir FileSystemController::GetSDMCModificationLoadRoot(u64 title_id) const { +    LOG_TRACE(Service_FS, "Opening SDMC mod load root for tid={:016X}", title_id); + +    if (sdmc_factory == nullptr) { +        return nullptr; +    } + +    return sdmc_factory->GetSDMCModificationLoadRoot(title_id); +} +  FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {      LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); @@ -733,20 +743,23 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove      }      using YuzuPath = Common::FS::YuzuPath; +    const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir); +    const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";      const auto rw_mode = FileSys::Mode::ReadWrite;      auto nand_directory =          vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); -    auto sd_directory = -        vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode); +    auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);      auto load_directory =          vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); +    auto sd_load_directory = +        vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read);      auto dump_directory =          vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);      if (bis_factory == nullptr) { -        bis_factory = -            std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); +        bis_factory = std::make_unique<FileSys::BISFactory>( +            nand_directory, std::move(load_directory), std::move(dump_directory));          system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,                                         bis_factory->GetSystemNANDContents());          system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, @@ -759,7 +772,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove      }      if (sdmc_factory == nullptr) { -        sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); +        sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory), +                                                              std::move(sd_load_directory));          system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,                                         sdmc_factory->GetSDMCContents());      } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index b6b1b9220..d387af3cb 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -115,6 +115,7 @@ public:      FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;      FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const; +    FileSys::VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;      FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;      FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 3af9881c2..db4d44c12 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -13,6 +13,7 @@  #include "common/common_types.h"  #include "common/hex_util.h"  #include "common/logging/log.h" +#include "common/settings.h"  #include "common/string_util.h"  #include "core/core.h"  #include "core/file_sys/directory.h" @@ -785,6 +786,10 @@ FSP_SRV::FSP_SRV(Core::System& system_)      };      // clang-format on      RegisterHandlers(functions); + +    if (Settings::values.enable_fs_access_log) { +        access_log_mode = AccessLogMode::SdCard; +    }  }  FSP_SRV::~FSP_SRV() = default; @@ -1041,9 +1046,9 @@ void FSP_SRV::DisableAutoSaveDataCreation(Kernel::HLERequestContext& ctx) {  void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx}; -    log_mode = rp.PopEnum<LogMode>(); +    access_log_mode = rp.PopEnum<AccessLogMode>(); -    LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode); +    LOG_DEBUG(Service_FS, "called, access_log_mode={}", access_log_mode);      IPC::ResponseBuilder rb{ctx, 2};      rb.Push(ResultSuccess); @@ -1054,7 +1059,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {      IPC::ResponseBuilder rb{ctx, 3};      rb.Push(ResultSuccess); -    rb.PushEnum(log_mode); +    rb.PushEnum(access_log_mode);  }  void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { @@ -1062,9 +1067,9 @@ void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {      auto log = Common::StringFromFixedZeroTerminatedBuffer(          reinterpret_cast<const char*>(raw.data()), raw.size()); -    LOG_DEBUG(Service_FS, "called, log='{}'", log); +    LOG_DEBUG(Service_FS, "called"); -    reporter.SaveFilesystemAccessReport(log_mode, std::move(log)); +    reporter.SaveFSAccessLog(log);      IPC::ResponseBuilder rb{ctx, 2};      rb.Push(ResultSuccess); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index ff7455a20..556708284 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -24,11 +24,10 @@ enum class AccessLogVersion : u32 {      Latest = V7_0_0,  }; -enum class LogMode : u32 { -    Off, +enum class AccessLogMode : u32 { +    None,      Log, -    RedirectToSdCard, -    LogToSdCard = Log | RedirectToSdCard, +    SdCard,  };  class FSP_SRV final : public ServiceFramework<FSP_SRV> { @@ -59,13 +58,12 @@ private:      FileSystemController& fsc;      const FileSys::ContentProvider& content_provider; +    const Core::Reporter& reporter;      FileSys::VirtualFile romfs;      u64 current_process_id = 0;      u32 access_log_program_index = 0; -    LogMode log_mode = LogMode::LogToSdCard; - -    const Core::Reporter& reporter; +    AccessLogMode access_log_mode = AccessLogMode::None;  };  } // namespace Service::FileSystem diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 7acad3798..1eb02aee2 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -314,6 +314,8 @@ void Controller_NPad::OnInit() {  void Controller_NPad::OnLoadInputDevices() {      const auto& players = Settings::values.players.GetValue(); + +    std::lock_guard lock{mutex};      for (std::size_t i = 0; i < players.size(); ++i) {          std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,                         players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, @@ -348,6 +350,8 @@ void Controller_NPad::OnRelease() {  }  void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { +    std::lock_guard lock{mutex}; +      const auto controller_idx = NPadIdToIndex(npad_id);      const auto controller_type = connected_controllers[controller_idx].type;      if (!connected_controllers[controller_idx].is_connected) { diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index c050c9a44..1409d82a2 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -6,6 +6,8 @@  #include <array>  #include <atomic> +#include <mutex> +  #include "common/bit_field.h"  #include "common/common_types.h"  #include "common/quaternion.h" @@ -563,6 +565,8 @@ private:      using MotionArray = std::array<          std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,          10>; + +    std::mutex mutex;      ButtonArray buttons;      StickArray sticks;      VibrationArray vibrations; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index fa6213d3c..d68b023d0 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -236,7 +236,7 @@ Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} {          {80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"},          {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},          {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, -        {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"}, +        {83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"},          {91, &Hid::ActivateGesture, "ActivateGesture"},          {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},          {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, @@ -710,6 +710,27 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {                  .IsSixAxisSensorAtRest());  } +void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp{ctx}; +    struct Parameters { +        Controller_NPad::DeviceHandle sixaxis_handle; +        INSERT_PADDING_WORDS_NOINIT(1); +        u64 applet_resource_user_id; +    }; + +    const auto parameters{rp.PopRaw<Parameters>()}; + +    LOG_WARNING( +        Service_HID, +        "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", +        parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, +        parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + +    IPC::ResponseBuilder rb{ctx, 3}; +    rb.Push(ResultSuccess); +    rb.Push(false); +} +  void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      struct Parameters { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index aa3307955..83fc2ea1d 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -100,6 +100,7 @@ private:      void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);      void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);      void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); +    void IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx);      void ActivateGesture(Kernel::HLERequestContext& ctx);      void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);      void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 311e4fb2d..794504314 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -51,6 +51,24 @@ struct hash<Service::LM::LogPacketHeaderEntry> {  } // namespace std  namespace Service::LM { +namespace { +std::string_view NameOf(LogSeverity severity) { +    switch (severity) { +    case LogSeverity::Trace: +        return "TRACE"; +    case LogSeverity::Info: +        return "INFO"; +    case LogSeverity::Warning: +        return "WARNING"; +    case LogSeverity::Error: +        return "ERROR"; +    case LogSeverity::Fatal: +        return "FATAL"; +    default: +        return "UNKNOWN"; +    } +} +} // Anonymous namespace  enum class LogDestination : u32 {      TargetManager = 1 << 0, @@ -262,33 +280,8 @@ private:          if (text_log) {              output_log += fmt::format("Log Text: {}\n", *text_log);          } - -        switch (entry.severity) { -        case LogSeverity::Trace: -            LOG_DEBUG(Service_LM, "LogManager TRACE ({}):\n{}", DestinationToString(destination), -                      output_log); -            break; -        case LogSeverity::Info: -            LOG_INFO(Service_LM, "LogManager INFO ({}):\n{}", DestinationToString(destination), -                     output_log); -            break; -        case LogSeverity::Warning: -            LOG_WARNING(Service_LM, "LogManager WARNING ({}):\n{}", -                        DestinationToString(destination), output_log); -            break; -        case LogSeverity::Error: -            LOG_ERROR(Service_LM, "LogManager ERROR ({}):\n{}", DestinationToString(destination), -                      output_log); -            break; -        case LogSeverity::Fatal: -            LOG_CRITICAL(Service_LM, "LogManager FATAL ({}):\n{}", DestinationToString(destination), -                         output_log); -            break; -        default: -            LOG_CRITICAL(Service_LM, "LogManager UNKNOWN ({}):\n{}", -                         DestinationToString(destination), output_log); -            break; -        } +        LOG_DEBUG(Service_LM, "LogManager {} ({}):\n{}", NameOf(entry.severity), +                  DestinationToString(destination), output_log);      }      static std::string DestinationToString(LogDestination destination) { diff --git a/src/core/hle/service/mii/manager.h b/src/core/hle/service/mii/manager.h index ec7efa5f7..8e048fc56 100644 --- a/src/core/hle/service/mii/manager.h +++ b/src/core/hle/service/mii/manager.h @@ -4,6 +4,8 @@  #pragma once +#include <array> +#include <vector>  #include "common/bit_field.h"  #include "common/common_funcs.h"  #include "common/uuid.h" diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index d1dbc659b..1d810562f 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -307,6 +307,9 @@ void NVFlinger::Compose() {  }  s64 NVFlinger::GetNextTicks() const { +    if (Settings::values.disable_fps_limit.GetValue()) { +        return 0; +    }      constexpr s64 max_hertz = 120LL;      return (1000000000 * (1LL << swap_interval)) / max_hertz;  } diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 4e1541630..663b83cd3 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -149,10 +149,10 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext      std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name;      fmt::memory_buffer buf; -    fmt::format_to(buf, "function '{}': port='{}' cmd_buf={{[0]=0x{:X}", function_name, -                   service_name, cmd_buf[0]); +    fmt::format_to(std::back_inserter(buf), "function '{}': port='{}' cmd_buf={{[0]=0x{:X}", +                   function_name, service_name, cmd_buf[0]);      for (int i = 1; i <= 8; ++i) { -        fmt::format_to(buf, ", [{}]=0x{:X}", i, cmd_buf[i]); +        fmt::format_to(std::back_inserter(buf), ", [{}]=0x{:X}", i, cmd_buf[i]);      }      buf.push_back('}'); diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp index 1beca417c..9c7f89475 100644 --- a/src/core/hle/service/spl/csrng.cpp +++ b/src/core/hle/service/spl/csrng.cpp @@ -9,7 +9,7 @@ namespace Service::SPL {  CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "csrng") {      static const FunctionInfo functions[] = { -        {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, +        {0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"},      };      RegisterHandlers(functions);  } diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index 0b5e2b7c3..ebb179aa8 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -10,6 +10,7 @@  #include <vector>  #include "common/logging/log.h"  #include "common/settings.h" +#include "core/hle/api_version.h"  #include "core/hle/ipc_helpers.h"  #include "core/hle/service/spl/csrng.h"  #include "core/hle/service/spl/module.h" @@ -24,7 +25,46 @@ Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> modu  Module::Interface::~Interface() = default; -void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { +void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp{ctx}; +    const auto config_item = rp.PopEnum<ConfigItem>(); + +    // This should call svcCallSecureMonitor with the appropriate args. +    // Since we do not have it implemented yet, we will use this for now. +    const auto smc_result = GetConfigImpl(config_item); +    const auto result_code = smc_result.Code(); + +    if (smc_result.Failed()) { +        LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, +                  result_code.raw); + +        IPC::ResponseBuilder rb{ctx, 2}; +        rb.Push(result_code); +    } + +    LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, +              result_code.raw, *smc_result); + +    IPC::ResponseBuilder rb{ctx, 4}; +    rb.Push(result_code); +    rb.Push(*smc_result); +} + +void Module::Interface::ModularExponentiate(Kernel::HLERequestContext& ctx) { +    UNIMPLEMENTED_MSG("ModularExponentiate is not implemented!"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetConfig(Kernel::HLERequestContext& ctx) { +    UNIMPLEMENTED_MSG("SetConfig is not implemented!"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GenerateRandomBytes(Kernel::HLERequestContext& ctx) {      LOG_DEBUG(Service_SPL, "called");      const std::size_t size = ctx.GetWriteBufferSize(); @@ -39,6 +79,88 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {      rb.Push(ResultSuccess);  } +void Module::Interface::IsDevelopment(Kernel::HLERequestContext& ctx) { +    UNIMPLEMENTED_MSG("IsDevelopment is not implemented!"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetBootReason(Kernel::HLERequestContext& ctx) { +    UNIMPLEMENTED_MSG("SetBootReason is not implemented!"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GetBootReason(Kernel::HLERequestContext& ctx) { +    UNIMPLEMENTED_MSG("GetBootReason is not implemented!"); + +    IPC::ResponseBuilder rb{ctx, 2}; +    rb.Push(ResultSecureMonitorNotImplemented); +} + +ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { +    switch (config_item) { +    case ConfigItem::DisableProgramVerification: +    case ConfigItem::DramId: +    case ConfigItem::SecurityEngineInterruptNumber: +    case ConfigItem::FuseVersion: +    case ConfigItem::HardwareType: +    case ConfigItem::HardwareState: +    case ConfigItem::IsRecoveryBoot: +    case ConfigItem::DeviceId: +    case ConfigItem::BootReason: +    case ConfigItem::MemoryMode: +    case ConfigItem::IsDevelopmentFunctionEnabled: +    case ConfigItem::KernelConfiguration: +    case ConfigItem::IsChargerHiZModeEnabled: +    case ConfigItem::QuestState: +    case ConfigItem::RegulatorType: +    case ConfigItem::DeviceUniqueKeyGeneration: +    case ConfigItem::Package2Hash: +        return ResultSecureMonitorNotImplemented; +    case ConfigItem::ExosphereApiVersion: +        // Get information about the current exosphere version. +        return MakeResult((u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | +                          (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | +                          (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | +                          (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()))); +    case ConfigItem::ExosphereNeedsReboot: +        // We are executing, so we aren't in the process of rebooting. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereNeedsShutdown: +        // We are executing, so we aren't in the process of shutting down. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereGitCommitHash: +        // Get information about the current exosphere git commit hash. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereHasRcmBugPatch: +        // Get information about whether this unit has the RCM bug patched. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereBlankProdInfo: +        // Get whether this unit should simulate a "blanked" PRODINFO. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereAllowCalWrites: +        // Get whether this unit should allow writing to the calibration partition. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereEmummcType: +        // Get what kind of emummc this unit has active. +        return MakeResult(u64{0}); +    case ConfigItem::ExospherePayloadAddress: +        // Gets the physical address of the reboot payload buffer, if one exists. +        return ResultSecureMonitorNotInitialized; +    case ConfigItem::ExosphereLogConfiguration: +        // Get the log configuration. +        return MakeResult(u64{0}); +    case ConfigItem::ExosphereForceEnableUsb30: +        // Get whether usb 3.0 should be force-enabled. +        return MakeResult(u64{0}); +    default: +        return ResultSecureMonitorInvalidArgument; +    } +} +  void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {      auto module = std::make_shared<Module>();      std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager); diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h index 71855c1bf..61630df80 100644 --- a/src/core/hle/service/spl/module.h +++ b/src/core/hle/service/spl/module.h @@ -6,6 +6,8 @@  #include <random>  #include "core/hle/service/service.h" +#include "core/hle/service/spl/spl_results.h" +#include "core/hle/service/spl/spl_types.h"  namespace Core {  class System; @@ -21,12 +23,21 @@ public:                             const char* name);          ~Interface() override; -        void GetRandomBytes(Kernel::HLERequestContext& ctx); +        // General +        void GetConfig(Kernel::HLERequestContext& ctx); +        void ModularExponentiate(Kernel::HLERequestContext& ctx); +        void SetConfig(Kernel::HLERequestContext& ctx); +        void GenerateRandomBytes(Kernel::HLERequestContext& ctx); +        void IsDevelopment(Kernel::HLERequestContext& ctx); +        void SetBootReason(Kernel::HLERequestContext& ctx); +        void GetBootReason(Kernel::HLERequestContext& ctx);      protected:          std::shared_ptr<Module> module;      private: +        ResultVal<u64> GetConfigImpl(ConfigItem config_item) const; +          std::mt19937 rng;      };  }; diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index fff3f3c42..20384042f 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -10,13 +10,13 @@ SPL::SPL(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GetRandomBytes"}, -        {11, nullptr, "IsDevelopment"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},      };      // clang-format on @@ -27,22 +27,22 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:mig") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"},          {2, nullptr, "GenerateAesKek"},          {3, nullptr, "LoadAesKey"},          {4, nullptr, "GenerateAesKey"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, -        {11, nullptr, "IsDevelopment"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"},          {14, nullptr, "DecryptAesKey"},          {15, nullptr, "CryptAesCtr"},          {16, nullptr, "ComputeCmac"},          {21, nullptr, "AllocateAesKeyslot"},          {22, nullptr, "DeallocateAesKeySlot"},          {23, nullptr, "GetAesKeyslotAvailableEvent"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},      };      // clang-format on @@ -53,16 +53,16 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:fs") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"},          {2, nullptr, "GenerateAesKek"},          {3, nullptr, "LoadAesKey"},          {4, nullptr, "GenerateAesKey"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},          {9, nullptr, "ImportLotusKey"},          {10, nullptr, "DecryptLotusMessage"}, -        {11, nullptr, "IsDevelopment"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"},          {12, nullptr, "GenerateSpecificAesKey"},          {14, nullptr, "DecryptAesKey"},          {15, nullptr, "CryptAesCtr"}, @@ -71,8 +71,8 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_)          {21, nullptr, "AllocateAesKeyslot"},          {22, nullptr, "DeallocateAesKeySlot"},          {23, nullptr, "GetAesKeyslotAvailableEvent"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},          {31, nullptr, "GetPackage2Hash"},      };      // clang-format on @@ -84,14 +84,14 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:ssl") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"},          {2, nullptr, "GenerateAesKek"},          {3, nullptr, "LoadAesKey"},          {4, nullptr, "GenerateAesKey"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GetRandomBytes"}, -        {11, nullptr, "IsDevelopment"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"},          {13, nullptr, "DecryptDeviceUniqueData"},          {14, nullptr, "DecryptAesKey"},          {15, nullptr, "CryptAesCtr"}, @@ -99,8 +99,8 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_)          {21, nullptr, "AllocateAesKeyslot"},          {22, nullptr, "DeallocateAesKeySlot"},          {23, nullptr, "GetAesKeyslotAvailableEvent"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},          {26, nullptr, "DecryptAndStoreSslClientCertKey"},          {27, nullptr, "ModularExponentiateWithSslClientCertKey"},      }; @@ -113,14 +113,14 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:es") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"},          {2, nullptr, "GenerateAesKek"},          {3, nullptr, "LoadAesKey"},          {4, nullptr, "GenerateAesKey"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, -        {11, nullptr, "IsDevelopment"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"},          {13, nullptr, "DecryptDeviceUniqueData"},          {14, nullptr, "DecryptAesKey"},          {15, nullptr, "CryptAesCtr"}, @@ -131,8 +131,8 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_)          {21, nullptr, "AllocateAesKeyslot"},          {22, nullptr, "DeallocateAesKeySlot"},          {23, nullptr, "GetAesKeyslotAvailableEvent"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},          {28, nullptr, "DecryptAndStoreDrmDeviceCertKey"},          {29, nullptr, "ModularExponentiateWithDrmDeviceCertKey"},          {31, nullptr, "PrepareEsArchiveKey"}, @@ -147,14 +147,14 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_)      : Interface(system_, std::move(module_), "spl:manu") {      // clang-format off      static const FunctionInfo functions[] = { -        {0, nullptr, "GetConfig"}, -        {1, nullptr, "ModularExponentiate"}, +        {0, &SPL::GetConfig, "GetConfig"}, +        {1, &SPL::ModularExponentiate, "ModularExponentiate"},          {2, nullptr, "GenerateAesKek"},          {3, nullptr, "LoadAesKey"},          {4, nullptr, "GenerateAesKey"}, -        {5, nullptr, "SetConfig"}, -        {7, &SPL::GetRandomBytes, "GetRandomBytes"}, -        {11, nullptr, "IsDevelopment"}, +        {5, &SPL::SetConfig, "SetConfig"}, +        {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, +        {11, &SPL::IsDevelopment, "IsDevelopment"},          {13, nullptr, "DecryptDeviceUniqueData"},          {14, nullptr, "DecryptAesKey"},          {15, nullptr, "CryptAesCtr"}, @@ -162,8 +162,8 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_)          {21, nullptr, "AllocateAesKeyslot"},          {22, nullptr, "DeallocateAesKeySlot"},          {23, nullptr, "GetAesKeyslotAvailableEvent"}, -        {24, nullptr, "SetBootReason"}, -        {25, nullptr, "GetBootReason"}, +        {24, &SPL::SetBootReason, "SetBootReason"}, +        {25, &SPL::GetBootReason, "GetBootReason"},          {30, nullptr, "ReencryptDeviceUniqueData"},      };      // clang-format on diff --git a/src/core/hle/service/spl/spl_results.h b/src/core/hle/service/spl/spl_results.h new file mode 100644 index 000000000..a07c61409 --- /dev/null +++ b/src/core/hle/service/spl/spl_results.h @@ -0,0 +1,31 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::SPL { + +// Description 0 - 99 +constexpr ResultCode ResultSecureMonitorError{ErrorModule::SPL, 0}; +constexpr ResultCode ResultSecureMonitorNotImplemented{ErrorModule::SPL, 1}; +constexpr ResultCode ResultSecureMonitorInvalidArgument{ErrorModule::SPL, 2}; +constexpr ResultCode ResultSecureMonitorBusy{ErrorModule::SPL, 3}; +constexpr ResultCode ResultSecureMonitorNoAsyncOperation{ErrorModule::SPL, 4}; +constexpr ResultCode ResultSecureMonitorInvalidAsyncOperation{ErrorModule::SPL, 5}; +constexpr ResultCode ResultSecureMonitorNotPermitted{ErrorModule::SPL, 6}; +constexpr ResultCode ResultSecureMonitorNotInitialized{ErrorModule::SPL, 7}; + +constexpr ResultCode ResultInvalidSize{ErrorModule::SPL, 100}; +constexpr ResultCode ResultUnknownSecureMonitorError{ErrorModule::SPL, 101}; +constexpr ResultCode ResultDecryptionFailed{ErrorModule::SPL, 102}; + +constexpr ResultCode ResultOutOfKeySlots{ErrorModule::SPL, 104}; +constexpr ResultCode ResultInvalidKeySlot{ErrorModule::SPL, 105}; +constexpr ResultCode ResultBootReasonAlreadySet{ErrorModule::SPL, 106}; +constexpr ResultCode ResultBootReasonNotSet{ErrorModule::SPL, 107}; +constexpr ResultCode ResultInvalidArgument{ErrorModule::SPL, 108}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl_types.h b/src/core/hle/service/spl/spl_types.h new file mode 100644 index 000000000..a654e7556 --- /dev/null +++ b/src/core/hle/service/spl/spl_types.h @@ -0,0 +1,232 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <span> + +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Service::SPL { + +constexpr size_t AES_128_KEY_SIZE = 0x10; + +namespace Smc { + +enum class FunctionId : u32 { +    SetConfig = 0xC3000401, +    GetConfig = 0xC3000002, +    GetResult = 0xC3000003, +    GetResultData = 0xC3000404, +    ModularExponentiate = 0xC3000E05, +    GenerateRandomBytes = 0xC3000006, +    GenerateAesKek = 0xC3000007, +    LoadAesKey = 0xC3000008, +    ComputeAes = 0xC3000009, +    GenerateSpecificAesKey = 0xC300000A, +    ComputeCmac = 0xC300040B, +    ReencryptDeviceUniqueData = 0xC300D60C, +    DecryptDeviceUniqueData = 0xC300100D, + +    ModularExponentiateWithStorageKey = 0xC300060F, +    PrepareEsDeviceUniqueKey = 0xC3000610, +    LoadPreparedAesKey = 0xC3000011, +    PrepareCommonEsTitleKey = 0xC3000012, + +    // Deprecated functions. +    LoadEsDeviceKey = 0xC300100C, +    DecryptAndStoreGcKey = 0xC300100E, + +    // Atmosphere functions. +    AtmosphereIramCopy = 0xF0000201, +    AtmosphereReadWriteRegister = 0xF0000002, + +    AtmosphereGetEmummcConfig = 0xF0000404, +}; + +enum class CipherMode { +    CbcEncrypt = 0, +    CbcDecrypt = 1, +    Ctr = 2, +}; + +enum class DeviceUniqueDataMode { +    DecryptDeviceUniqueData = 0, +    DecryptAndStoreGcKey = 1, +    DecryptAndStoreEsDeviceKey = 2, +    DecryptAndStoreSslKey = 3, +    DecryptAndStoreDrmDeviceCertKey = 4, +}; + +enum class ModularExponentiateWithStorageKeyMode { +    Gc = 0, +    Ssl = 1, +    DrmDeviceCert = 2, +}; + +enum class EsCommonKeyType { +    TitleKey = 0, +    ArchiveKey = 1, +}; + +struct AsyncOperationKey { +    u64 value; +}; + +} // namespace Smc + +enum class HardwareType { +    Icosa = 0, +    Copper = 1, +    Hoag = 2, +    Iowa = 3, +    Calcio = 4, +    Aula = 5, +}; + +enum class SocType { +    Erista = 0, +    Mariko = 1, +}; + +enum class HardwareState { +    Development = 0, +    Production = 1, +}; + +enum class MemoryArrangement { +    Standard = 0, +    StandardForAppletDev = 1, +    StandardForSystemDev = 2, +    Expanded = 3, +    ExpandedForAppletDev = 4, + +    // Note: Dynamic is not official. +    // Atmosphere uses it to maintain compatibility with firmwares prior to 6.0.0, +    // which removed the explicit retrieval of memory arrangement from PM. +    Dynamic = 5, +    Count, +}; + +enum class BootReason { +    Unknown = 0, +    AcOk = 1, +    OnKey = 2, +    RtcAlarm1 = 3, +    RtcAlarm2 = 4, +}; + +struct BootReasonValue { +    union { +        u32 value{}; + +        BitField<0, 8, u32> power_intr; +        BitField<8, 8, u32> rtc_intr; +        BitField<16, 8, u32> nv_erc; +        BitField<24, 8, u32> boot_reason; +    }; +}; +static_assert(sizeof(BootReasonValue) == sizeof(u32), "BootReasonValue definition!"); + +struct AesKey { +    std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + +    std::span<u8> AsBytes() { +        return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; +    } + +    std::span<const u8> AsBytes() const { +        return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; +    } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AesKey definition!"); + +struct IvCtr { +    std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + +    std::span<u8> AsBytes() { +        return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; +    } + +    std::span<const u8> AsBytes() const { +        return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; +    } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "IvCtr definition!"); + +struct Cmac { +    std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + +    std::span<u8> AsBytes() { +        return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; +    } + +    std::span<const u8> AsBytes() const { +        return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; +    } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "Cmac definition!"); + +struct AccessKey { +    std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + +    std::span<u8> AsBytes() { +        return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; +    } + +    std::span<const u8> AsBytes() const { +        return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; +    } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AccessKey definition!"); + +struct KeySource { +    std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + +    std::span<u8> AsBytes() { +        return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; +    } + +    std::span<const u8> AsBytes() const { +        return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; +    } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "KeySource definition!"); + +enum class ConfigItem : u32 { +    // Standard config items. +    DisableProgramVerification = 1, +    DramId = 2, +    SecurityEngineInterruptNumber = 3, +    FuseVersion = 4, +    HardwareType = 5, +    HardwareState = 6, +    IsRecoveryBoot = 7, +    DeviceId = 8, +    BootReason = 9, +    MemoryMode = 10, +    IsDevelopmentFunctionEnabled = 11, +    KernelConfiguration = 12, +    IsChargerHiZModeEnabled = 13, +    QuestState = 14, +    RegulatorType = 15, +    DeviceUniqueKeyGeneration = 16, +    Package2Hash = 17, + +    // Extension config items for exosphere. +    ExosphereApiVersion = 65000, +    ExosphereNeedsReboot = 65001, +    ExosphereNeedsShutdown = 65002, +    ExosphereGitCommitHash = 65003, +    ExosphereHasRcmBugPatch = 65004, +    ExosphereBlankProdInfo = 65005, +    ExosphereAllowCalWrites = 65006, +    ExosphereEmummcType = 65007, +    ExospherePayloadAddress = 65008, +    ExosphereLogConfiguration = 65009, +    ExosphereForceEnableUsb30 = 65010, +}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp index bf4402308..c634b6abd 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -125,7 +125,7 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati          return ERROR_TIME_NOT_FOUND;      } -    vfs_file = zoneinfo_dir->GetFile(location_name); +    vfs_file = zoneinfo_dir->GetFileRelative(location_name);      if (!vfs_file) {          LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.",                    time_zone_binary_titleid, location_name); diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index ec2a16e62..82b0f535a 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -195,7 +195,9 @@ json GetHLERequestContextData(Kernel::HLERequestContext& ctx, Core::Memory::Memo  namespace Core { -Reporter::Reporter(System& system_) : system(system_) {} +Reporter::Reporter(System& system_) : system(system_) { +    ClearFSAccessLog(); +}  Reporter::~Reporter() = default; @@ -362,22 +364,12 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result,      SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));  } -void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, -                                          std::string log_message) const { -    if (!IsReportingEnabled()) -        return; - -    const auto timestamp = GetTimestamp(); -    const auto title_id = system.CurrentProcess()->GetTitleID(); -    json out; +void Reporter::SaveFSAccessLog(std::string_view log_message) const { +    const auto access_log_path = +        Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt"; -    out["yuzu_version"] = GetYuzuVersionData(); -    out["report_common"] = GetReportCommonData(title_id, ResultSuccess, timestamp); - -    out["log_mode"] = fmt::format("{:08X}", static_cast<u32>(log_mode)); -    out["log_message"] = std::move(log_message); - -    SaveToFile(std::move(out), GetPath("filesystem_access_report", title_id, timestamp)); +    void(Common::FS::AppendStringToFile(access_log_path, Common::FS::FileType::TextFile, +                                        log_message));  }  void Reporter::SaveUserReport() const { @@ -392,6 +384,18 @@ void Reporter::SaveUserReport() const {                 GetPath("user_report", title_id, timestamp));  } +void Reporter::ClearFSAccessLog() const { +    const auto access_log_path = +        Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt"; + +    Common::FS::IOFile access_log_file{access_log_path, Common::FS::FileAccessMode::Write, +                                       Common::FS::FileType::TextFile}; + +    if (!access_log_file.IsOpen()) { +        LOG_ERROR(Common_Filesystem, "Failed to clear the filesystem access log."); +    } +} +  bool Reporter::IsReportingEnabled() const {      return Settings::values.reporting_services;  } diff --git a/src/core/reporter.h b/src/core/reporter.h index 6fb6ebffa..6e9edeea3 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -16,10 +16,6 @@ namespace Kernel {  class HLERequestContext;  } // namespace Kernel -namespace Service::FileSystem { -enum class LogMode : u32; -} -  namespace Service::LM {  struct LogMessage;  } // namespace Service::LM @@ -69,14 +65,15 @@ public:                           std::optional<std::string> custom_text_main = {},                           std::optional<std::string> custom_text_detail = {}) const; -    void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, -                                    std::string log_message) const; +    void SaveFSAccessLog(std::string_view log_message) const;      // Can be used anywhere to generate a backtrace and general info report at any point during      // execution. Not intended to be used for anything other than debugging or testing.      void SaveUserReport() const;  private: +    void ClearFSAccessLog() const; +      bool IsReportingEnabled() const;      System& system; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index ad1a9ffb4..d4c23ced2 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -230,6 +230,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,               Settings::values.use_asynchronous_gpu_emulation.GetValue());      AddField(field_type, "Renderer_UseNvdecEmulation",               Settings::values.use_nvdec_emulation.GetValue()); +    AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());      AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());      AddField(field_type, "Renderer_UseAssemblyShaders",               Settings::values.use_assembly_shaders.GetValue()); diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp index a335e6da1..3b052ffb2 100644 --- a/src/input_common/mouse/mouse_input.cpp +++ b/src/input_common/mouse/mouse_input.cpp @@ -2,25 +2,23 @@  // Licensed under GPLv2+  // Refer to the license.txt file included. +#include <stop_token> +#include <thread> +  #include "common/settings.h"  #include "input_common/mouse/mouse_input.h"  namespace MouseInput {  Mouse::Mouse() { -    update_thread = std::thread(&Mouse::UpdateThread, this); +    update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });  } -Mouse::~Mouse() { -    update_thread_running = false; -    if (update_thread.joinable()) { -        update_thread.join(); -    } -} +Mouse::~Mouse() = default; -void Mouse::UpdateThread() { +void Mouse::UpdateThread(std::stop_token stop_token) {      constexpr int update_time = 10; -    while (update_thread_running) { +    while (!stop_token.stop_requested()) {          for (MouseInfo& info : mouse_info) {              const Common::Vec3f angular_direction{                  -info.tilt_direction.y, diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h index 5a971ad67..c8bae99c1 100644 --- a/src/input_common/mouse/mouse_input.h +++ b/src/input_common/mouse/mouse_input.h @@ -6,6 +6,7 @@  #include <array>  #include <mutex> +#include <stop_token>  #include <thread>  #include "common/common_types.h" @@ -85,7 +86,7 @@ public:      [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;  private: -    void UpdateThread(); +    void UpdateThread(std::stop_token stop_token);      void UpdateYuzuSettings();      void StopPanning(); @@ -105,12 +106,11 @@ private:      u16 buttons{};      u16 toggle_buttons{};      u16 lock_buttons{}; -    std::thread update_thread; +    std::jthread update_thread;      MouseButton last_button{MouseButton::Undefined};      std::array<MouseInfo, 7> mouse_info;      Common::SPSCQueue<MouseStatus> mouse_queue;      bool configuring{false}; -    bool update_thread_running{true};      int mouse_panning_timout{};  };  } // namespace MouseInput diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp index d96104a4e..758f7af1f 100644 --- a/src/input_common/mouse/mouse_poller.cpp +++ b/src/input_common/mouse/mouse_poller.cpp @@ -2,6 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm> +#include <memory>  #include <mutex>  #include <utility> diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp index e94ba197b..5b24fd8bf 100644 --- a/src/input_common/touch_from_button.cpp +++ b/src/input_common/touch_from_button.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include "common/settings.h"  #include "core/frontend/framebuffer_layout.h"  #include "input_common/touch_from_button.h" diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp index e241f8be5..2dc7b5d5e 100644 --- a/src/tests/common/host_memory.cpp +++ b/src/tests/common/host_memory.cpp @@ -5,11 +5,13 @@  #include <catch2/catch.hpp>  #include "common/host_memory.h" +#include "common/literals.h"  using Common::HostMemory; +using namespace Common::Literals;  static constexpr size_t VIRTUAL_SIZE = 1ULL << 39; -static constexpr size_t BACKING_SIZE = 4ULL * 1024 * 1024 * 1024; +static constexpr size_t BACKING_SIZE = 4_GiB;  TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {      { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 47190c464..e31eb30c0 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -237,6 +237,7 @@ add_library(video_core STATIC      texture_cache/util.cpp      texture_cache/util.h      textures/astc.h +    textures/astc.cpp      textures/decoders.cpp      textures/decoders.h      textures/texture.cpp @@ -292,6 +293,7 @@ endif()  if (MSVC)      target_compile_options(video_core PRIVATE          /we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data +        /we4244 # 'var' : conversion from integer to 'type', possible loss of data          /we4456 # Declaration of 'identifier' hides previous local declaration          /we4457 # Declaration of 'identifier' hides function parameter          /we4458 # Declaration of 'identifier' hides class member diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index a39505903..b121d36a3 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -256,6 +256,16 @@ public:          stream_score += score;      } +    /// Sets the new frame tick +    void SetFrameTick(u64 new_frame_tick) noexcept { +        frame_tick = new_frame_tick; +    } + +    /// Returns the new frame tick +    [[nodiscard]] u64 FrameTick() const noexcept { +        return frame_tick; +    } +      /// Returns the likeliness of this being a stream buffer      [[nodiscard]] int StreamScore() const noexcept {          return stream_score; @@ -586,6 +596,7 @@ private:      RasterizerInterface* rasterizer = nullptr;      VAddr cpu_addr = 0;      Words words; +    u64 frame_tick = 0;      BufferFlagBits flags{};      int stream_score = 0;  }; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index d371b842f..cad7f902d 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -18,6 +18,7 @@  #include "common/common_types.h"  #include "common/div_ceil.h" +#include "common/literals.h"  #include "common/microprofile.h"  #include "common/scope_exit.h"  #include "common/settings.h" @@ -47,8 +48,11 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;  constexpr u32 NUM_STORAGE_BUFFERS = 16;  constexpr u32 NUM_STAGES = 5; +using namespace Common::Literals; +  template <typename P>  class BufferCache { +      // Page size for caching purposes.      // This is unrelated to the CPU page size and it can be changed as it seems optimal.      static constexpr u32 PAGE_BITS = 16; @@ -65,6 +69,9 @@ class BufferCache {      static constexpr BufferId NULL_BUFFER_ID{0}; +    static constexpr u64 EXPECTED_MEMORY = 512_MiB; +    static constexpr u64 CRITICAL_MEMORY = 1_GiB; +      using Maxwell = Tegra::Engines::Maxwell3D::Regs;      using Runtime = typename P::Runtime; @@ -92,7 +99,7 @@ class BufferCache {      };  public: -    static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = 4096; +    static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);      explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,                           Tegra::Engines::Maxwell3D& maxwell3d_, @@ -188,6 +195,8 @@ private:                 ((cpu_addr + size) & ~Core::Memory::PAGE_MASK);      } +    void RunGarbageCollector(); +      void BindHostIndexBuffer();      void BindHostVertexBuffers(); @@ -243,6 +252,8 @@ private:      template <bool insert>      void ChangeRegister(BufferId buffer_id); +    void TouchBuffer(Buffer& buffer) const noexcept; +      bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);      bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size); @@ -255,6 +266,10 @@ private:      void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies); +    void DownloadBufferMemory(Buffer& buffer_id); + +    void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size); +      void DeleteBuffer(BufferId buffer_id);      void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id); @@ -319,6 +334,10 @@ private:      size_t immediate_buffer_capacity = 0;      std::unique_ptr<u8[]> immediate_buffer_alloc; +    typename SlotVector<Buffer>::Iterator deletion_iterator; +    u64 frame_tick = 0; +    u64 total_used_memory = 0; +      std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table;  }; @@ -332,6 +351,28 @@ BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,        gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, runtime{runtime_} {      // Ensure the first slot is used for the null buffer      void(slot_buffers.insert(runtime, NullBufferParams{})); +    deletion_iterator = slot_buffers.end(); +} + +template <class P> +void BufferCache<P>::RunGarbageCollector() { +    const bool aggressive_gc = total_used_memory >= CRITICAL_MEMORY; +    const u64 ticks_to_destroy = aggressive_gc ? 60 : 120; +    int num_iterations = aggressive_gc ? 64 : 32; +    for (; num_iterations > 0; --num_iterations) { +        if (deletion_iterator == slot_buffers.end()) { +            deletion_iterator = slot_buffers.begin(); +        } +        ++deletion_iterator; +        if (deletion_iterator == slot_buffers.end()) { +            break; +        } +        const auto [buffer_id, buffer] = *deletion_iterator; +        if (buffer->FrameTick() + ticks_to_destroy < frame_tick) { +            DownloadBufferMemory(*buffer); +            DeleteBuffer(buffer_id); +        } +    }  }  template <class P> @@ -349,6 +390,10 @@ void BufferCache<P>::TickFrame() {      const bool skip_preferred = hits * 256 < shots * 251;      uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0; +    if (Settings::values.use_caches_gc.GetValue() && total_used_memory >= EXPECTED_MEMORY) { +        RunGarbageCollector(); +    } +    ++frame_tick;      delayed_destruction_ring.Tick();  } @@ -372,48 +417,7 @@ void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) {  template <class P>  void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) {      ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { -        boost::container::small_vector<BufferCopy, 1> copies; -        u64 total_size_bytes = 0; -        u64 largest_copy = 0; -        buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { -            copies.push_back(BufferCopy{ -                .src_offset = range_offset, -                .dst_offset = total_size_bytes, -                .size = range_size, -            }); -            total_size_bytes += range_size; -            largest_copy = std::max(largest_copy, range_size); -        }); -        if (total_size_bytes == 0) { -            return; -        } -        MICROPROFILE_SCOPE(GPU_DownloadMemory); - -        if constexpr (USE_MEMORY_MAPS) { -            auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); -            const u8* const mapped_memory = download_staging.mapped_span.data(); -            const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); -            for (BufferCopy& copy : copies) { -                // Modify copies to have the staging offset in mind -                copy.dst_offset += download_staging.offset; -            } -            runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); -            runtime.Finish(); -            for (const BufferCopy& copy : copies) { -                const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; -                // Undo the modified offset -                const u64 dst_offset = copy.dst_offset - download_staging.offset; -                const u8* copy_mapped_memory = mapped_memory + dst_offset; -                cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size); -            } -        } else { -            const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); -            for (const BufferCopy& copy : copies) { -                buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); -                const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; -                cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size); -            } -        } +        DownloadBufferMemory(buffer, cpu_addr, size);      });  } @@ -640,6 +644,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {  template <class P>  void BufferCache<P>::BindHostIndexBuffer() {      Buffer& buffer = slot_buffers[index_buffer.buffer_id]; +    TouchBuffer(buffer);      const u32 offset = buffer.Offset(index_buffer.cpu_addr);      const u32 size = index_buffer.size;      SynchronizeBuffer(buffer, index_buffer.cpu_addr, size); @@ -658,6 +663,7 @@ void BufferCache<P>::BindHostVertexBuffers() {      for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {          const Binding& binding = vertex_buffers[index];          Buffer& buffer = slot_buffers[binding.buffer_id]; +        TouchBuffer(buffer);          SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);          if (!flags[Dirty::VertexBuffer0 + index]) {              continue; @@ -693,6 +699,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32      const VAddr cpu_addr = binding.cpu_addr;      const u32 size = binding.size;      Buffer& buffer = slot_buffers[binding.buffer_id]; +    TouchBuffer(buffer);      const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&                                   size <= uniform_buffer_skip_cache_size &&                                   !buffer.IsRegionGpuModified(cpu_addr, size); @@ -744,6 +751,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {      ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) {          const Binding& binding = storage_buffers[stage][index];          Buffer& buffer = slot_buffers[binding.buffer_id]; +        TouchBuffer(buffer);          const u32 size = binding.size;          SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -766,6 +774,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {      for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {          const Binding& binding = transform_feedback_buffers[index];          Buffer& buffer = slot_buffers[binding.buffer_id]; +        TouchBuffer(buffer);          const u32 size = binding.size;          SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -784,6 +793,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {      ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) {          const Binding& binding = compute_uniform_buffers[index];          Buffer& buffer = slot_buffers[binding.buffer_id]; +        TouchBuffer(buffer);          const u32 size = binding.size;          SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -803,6 +813,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {      ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) {          const Binding& binding = compute_storage_buffers[index];          Buffer& buffer = slot_buffers[binding.buffer_id]; +        TouchBuffer(buffer);          const u32 size = binding.size;          SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -1101,6 +1112,7 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {      const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);      const u32 size = static_cast<u32>(overlap.end - overlap.begin);      const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); +    TouchBuffer(slot_buffers[new_buffer_id]);      for (const BufferId overlap_id : overlap.ids) {          JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);      } @@ -1122,8 +1134,14 @@ template <class P>  template <bool insert>  void BufferCache<P>::ChangeRegister(BufferId buffer_id) {      const Buffer& buffer = slot_buffers[buffer_id]; +    const auto size = buffer.SizeBytes(); +    if (insert) { +        total_used_memory += Common::AlignUp(size, 1024); +    } else { +        total_used_memory -= Common::AlignUp(size, 1024); +    }      const VAddr cpu_addr_begin = buffer.CpuAddr(); -    const VAddr cpu_addr_end = cpu_addr_begin + buffer.SizeBytes(); +    const VAddr cpu_addr_end = cpu_addr_begin + size;      const u64 page_begin = cpu_addr_begin / PAGE_SIZE;      const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE);      for (u64 page = page_begin; page != page_end; ++page) { @@ -1136,6 +1154,11 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {  }  template <class P> +void BufferCache<P>::TouchBuffer(Buffer& buffer) const noexcept { +    buffer.SetFrameTick(frame_tick); +} + +template <class P>  bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {      if (buffer.CpuAddr() == 0) {          return true; @@ -1212,6 +1235,57 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,  }  template <class P> +void BufferCache<P>::DownloadBufferMemory(Buffer& buffer) { +    DownloadBufferMemory(buffer, buffer.CpuAddr(), buffer.SizeBytes()); +} + +template <class P> +void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 size) { +    boost::container::small_vector<BufferCopy, 1> copies; +    u64 total_size_bytes = 0; +    u64 largest_copy = 0; +    buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { +        copies.push_back(BufferCopy{ +            .src_offset = range_offset, +            .dst_offset = total_size_bytes, +            .size = range_size, +        }); +        total_size_bytes += range_size; +        largest_copy = std::max(largest_copy, range_size); +    }); +    if (total_size_bytes == 0) { +        return; +    } +    MICROPROFILE_SCOPE(GPU_DownloadMemory); + +    if constexpr (USE_MEMORY_MAPS) { +        auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); +        const u8* const mapped_memory = download_staging.mapped_span.data(); +        const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); +        for (BufferCopy& copy : copies) { +            // Modify copies to have the staging offset in mind +            copy.dst_offset += download_staging.offset; +        } +        runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); +        runtime.Finish(); +        for (const BufferCopy& copy : copies) { +            const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; +            // Undo the modified offset +            const u64 dst_offset = copy.dst_offset - download_staging.offset; +            const u8* copy_mapped_memory = mapped_memory + dst_offset; +            cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size); +        } +    } else { +        const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); +        for (const BufferCopy& copy : copies) { +            buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); +            const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; +            cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size); +        } +    } +} + +template <class P>  void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {      const auto scalar_replace = [buffer_id](Binding& binding) {          if (binding.buffer_id == buffer_id) { @@ -1236,6 +1310,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {      Unregister(buffer_id);      delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); +    slot_buffers.erase(buffer_id);      NotifyBufferDeletion();  } diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h index 8a2a6c360..3e135a2a6 100644 --- a/src/video_core/command_classes/codecs/codec.h +++ b/src/video_core/command_classes/codecs/codec.h @@ -14,10 +14,18 @@ extern "C" {  #pragma GCC diagnostic push  #pragma GCC diagnostic ignored "-Wconversion"  #endif +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4242) // conversion from 'type' to 'type', possible loss of data +#pragma warning(disable : 4244) // conversion from 'type' to 'type', possible loss of data +#endif  #include <libavcodec/avcodec.h>  #if defined(__GNUC__) || defined(__clang__)  #pragma GCC diagnostic pop  #endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif  }  namespace Tegra { diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp index 0a8b82f2b..5faf8c0f1 100644 --- a/src/video_core/command_classes/vic.cpp +++ b/src/video_core/command_classes/vic.cpp @@ -3,7 +3,28 @@  // Refer to the license.txt file included.  #include <array> + +extern "C" { +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#ifdef _MSC_VER +#pragma warning(disable : 4244) // conversion from 'type' to 'type', possible loss of data +#pragma warning(push) +#endif +#include <libswscale/swscale.h> +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +} +  #include "common/assert.h" +#include "common/logging/log.h" +  #include "video_core/command_classes/nvdec.h"  #include "video_core/command_classes/vic.h"  #include "video_core/engines/maxwell_3d.h" @@ -11,10 +32,6 @@  #include "video_core/memory_manager.h"  #include "video_core/textures/decoders.h" -extern "C" { -#include <libswscale/swscale.h> -} -  namespace Tegra {  Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_) diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index ffed42a29..335383955 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -242,6 +242,7 @@ public:                      return 4;                  default:                      UNREACHABLE(); +                    return 1;                  }              } diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 703e34587..c37f15bfd 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -11,12 +11,8 @@  #define UNIFORM(n)  #define BINDING_INPUT_BUFFER 0  #define BINDING_ENC_BUFFER 1 -#define BINDING_6_TO_8_BUFFER 2 -#define BINDING_7_TO_8_BUFFER 3 -#define BINDING_8_TO_8_BUFFER 4 -#define BINDING_BYTE_TO_16_BUFFER 5 -#define BINDING_SWIZZLE_BUFFER 6 -#define BINDING_OUTPUT_IMAGE 7 +#define BINDING_SWIZZLE_BUFFER 2 +#define BINDING_OUTPUT_IMAGE 3  #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv @@ -26,10 +22,6 @@  #define BINDING_SWIZZLE_BUFFER 0  #define BINDING_INPUT_BUFFER 1  #define BINDING_ENC_BUFFER 2 -#define BINDING_6_TO_8_BUFFER 3 -#define BINDING_7_TO_8_BUFFER 4 -#define BINDING_8_TO_8_BUFFER 5 -#define BINDING_BYTE_TO_16_BUFFER 6  #define BINDING_OUTPUT_IMAGE 0  #endif @@ -76,19 +68,6 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 {  layout(binding = BINDING_ENC_BUFFER, std430) readonly buffer EncodingsValues {      EncodingData encoding_values[];  }; -// ASTC Precompiled tables -layout(binding = BINDING_6_TO_8_BUFFER, std430) readonly buffer REPLICATE_6_BIT_TO_8 { -    uint REPLICATE_6_BIT_TO_8_TABLE[]; -}; -layout(binding = BINDING_7_TO_8_BUFFER, std430) readonly buffer REPLICATE_7_BIT_TO_8 { -    uint REPLICATE_7_BIT_TO_8_TABLE[]; -}; -layout(binding = BINDING_8_TO_8_BUFFER, std430) readonly buffer REPLICATE_8_BIT_TO_8 { -    uint REPLICATE_8_BIT_TO_8_TABLE[]; -}; -layout(binding = BINDING_BYTE_TO_16_BUFFER, std430) readonly buffer REPLICATE_BYTE_TO_16 { -    uint REPLICATE_BYTE_TO_16_TABLE[]; -};  layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; @@ -139,6 +118,19 @@ const uint REPLICATE_4_BIT_TO_6_TABLE[16] =  const uint REPLICATE_5_BIT_TO_6_TABLE[32] =      uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 33, 35, 37, 39, 41, 43, 45,             47, 49, 51, 53, 55, 57, 59, 61, 63); +const uint REPLICATE_6_BIT_TO_8_TABLE[64] = +    uint[](0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 65, 69, 73, 77, 81, 85, 89, +           93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, +           166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, +           239, 243, 247, 251, 255); +const uint REPLICATE_7_BIT_TO_8_TABLE[128] = +    uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, +           46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, +           90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, +           129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, +           165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, +           201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, +           237, 239, 241, 243, 245, 247, 249, 251, 253, 255);  // Input ASTC texture globals  uint current_index = 0; @@ -207,8 +199,7 @@ uint Replicate(uint val, uint num_bits, uint to_bit) {  }  uvec4 ReplicateByteTo16(uvec4 value) { -    return uvec4(REPLICATE_BYTE_TO_16_TABLE[value.x], REPLICATE_BYTE_TO_16_TABLE[value.y], -                 REPLICATE_BYTE_TO_16_TABLE[value.z], REPLICATE_BYTE_TO_16_TABLE[value.w]); +    return value * 0x101;  }  uint ReplicateBitTo7(uint value) { @@ -236,7 +227,7 @@ uint FastReplicateTo8(uint value, uint num_bits) {      case 7:          return REPLICATE_7_BIT_TO_8_TABLE[value];      case 8: -        return REPLICATE_8_BIT_TO_8_TABLE[value]; +        return value;      }      return Replicate(value, num_bits, 8);  } @@ -763,7 +754,7 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {      case 1: {          READ_UINT_VALUES(2)          uint L0 = (v[0] >> 2) | (v[1] & 0xC0); -        uint L1 = max(L0 + (v[1] & 0x3F), 0xFFU); +        uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU);          ep1 = uvec4(0xFF, L0, L0, L0);          ep2 = uvec4(0xFF, L1, L1, L1);          break; @@ -1327,6 +1318,9 @@ void main() {      offset += swizzle;      const ivec3 coord = ivec3(gl_GlobalInvocationID * uvec3(block_dims, 1)); +    if (any(greaterThanEqual(coord, imageSize(dest_image)))) { +        return; +    }      uint block_index =          pos.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + pos.y * gl_WorkGroupSize.x + pos.x; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index f968b5b16..07939432f 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -4,10 +4,10 @@  #pragma once -#include <atomic>  #include <functional>  #include <optional>  #include <span> +#include <stop_token>  #include "common/common_types.h"  #include "video_core/engines/fermi_2d.h"  #include "video_core/gpu.h" @@ -123,7 +123,7 @@ public:      virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}      /// Initialize disk cached resources for the game being emulated -    virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, +    virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,                                     const DiskResourceLoadCallback& callback) {}      /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 320ee8d30..63d8ad42a 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -42,6 +42,8 @@ public:      [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; +    [[nodiscard]] virtual std::string GetDeviceVendor() const = 0; +      // Getter/setter functions:      // ------------------------ diff --git a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp index 3e4d88c30..e8d8d2aa5 100644 --- a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp @@ -454,7 +454,7 @@ private:      template <typename... Args>      void AddExpression(std::string_view text, Args&&... args) { -        shader_source += fmt::format(text, std::forward<Args>(args)...); +        shader_source += fmt::format(fmt::runtime(text), std::forward<Args>(args)...);      }      template <typename... Args> diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 3f4532ca7..3b00614e7 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -202,13 +202,13 @@ Device::Device() {          LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");          throw std::runtime_error{"Insufficient version"};      } -    const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); +    vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));      const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));      const std::vector extensions = GetExtensions(); -    const bool is_nvidia = vendor == "NVIDIA Corporation"; -    const bool is_amd = vendor == "ATI Technologies Inc."; -    const bool is_intel = vendor == "Intel"; +    const bool is_nvidia = vendor_name == "NVIDIA Corporation"; +    const bool is_amd = vendor_name == "ATI Technologies Inc."; +    const bool is_intel = vendor_name == "Intel";  #ifdef __unix__      const bool is_linux = true; @@ -275,6 +275,56 @@ Device::Device() {      }  } +std::string Device::GetVendorName() const { +    if (vendor_name == "NVIDIA Corporation") { +        return "NVIDIA"; +    } +    if (vendor_name == "ATI Technologies Inc.") { +        return "AMD"; +    } +    if (vendor_name == "Intel") { +        // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris. +        // Simply return `INTEL` for those as well as the Windows driver. +        return "INTEL"; +    } +    if (vendor_name == "Intel Open Source Technology Center") { +        return "I965"; +    } +    if (vendor_name == "Mesa Project") { +        return "I915"; +    } +    if (vendor_name == "Mesa/X.org") { +        // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return +        // MESA instead of one of those driver names. +        return "MESA"; +    } +    if (vendor_name == "AMD") { +        return "RADEONSI"; +    } +    if (vendor_name == "nouveau") { +        return "NOUVEAU"; +    } +    if (vendor_name == "X.Org") { +        return "R600"; +    } +    if (vendor_name == "Collabora Ltd") { +        return "ZINK"; +    } +    if (vendor_name == "Intel Corporation") { +        return "OPENSWR"; +    } +    if (vendor_name == "Microsoft Corporation") { +        return "D3D12"; +    } +    if (vendor_name == "NVIDIA") { +        // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default +        // strategy would have returned `NVIDIA` here for this driver, the same result as the +        // proprietary driver. +        return "TEGRA"; +    } +    return vendor_name; +} +  Device::Device(std::nullptr_t) {      max_uniform_buffers.fill(std::numeric_limits<u32>::max());      uniform_buffer_alignment = 4; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index f24bd0c7b..2c2b13767 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -22,6 +22,8 @@ public:      explicit Device();      explicit Device(std::nullptr_t); +    [[nodiscard]] std::string GetVendorName() const; +      u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept {          return max_uniform_buffers[static_cast<std::size_t>(shader_type)];      } @@ -130,6 +132,7 @@ private:      static bool TestVariableAoffi();      static bool TestPreciseBug(); +    std::string vendor_name;      std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};      std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};      size_t uniform_buffer_alignment{}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index f87bb269b..eb8bdaa85 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -351,7 +351,7 @@ void RasterizerOpenGL::SetupShaders(bool is_indexed) {      }  } -void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, +void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_loading,                                           const VideoCore::DiskResourceLoadCallback& callback) {      shader_cache.LoadDiskCache(title_id, stop_loading, callback);  } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 76298517f..9995a563b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -94,7 +94,7 @@ public:                                 const Tegra::Engines::Fermi2D::Config& copy_config) override;      bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,                             u32 pixel_stride) override; -    void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, +    void LoadDiskResources(u64 title_id, std::stop_token stop_loading,                             const VideoCore::DiskResourceLoadCallback& callback) override;      /// Returns true when there are commands queued to the OpenGL server. diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 5cf7cd151..5a01c59ec 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -331,7 +331,7 @@ ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,  ShaderCacheOpenGL::~ShaderCacheOpenGL() = default; -void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, +void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, std::stop_token stop_loading,                                        const VideoCore::DiskResourceLoadCallback& callback) {      disk_cache.BindTitleID(title_id);      const std::optional transferable = disk_cache.LoadTransferable(); @@ -372,7 +372,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop          const auto scope = context->Acquire();          for (std::size_t i = begin; i < end; ++i) { -            if (stop_loading) { +            if (stop_loading.stop_requested()) {                  return;              }              const auto& entry = (*transferable)[i]; @@ -435,7 +435,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop          precompiled_cache_altered = true;          return;      } -    if (stop_loading) { +    if (stop_loading.stop_requested()) {          return;      } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 2aed0697e..b30308b6f 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -127,7 +127,7 @@ public:      ~ShaderCacheOpenGL() override;      /// Loads disk cache for the current game -    void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, +    void LoadDiskCache(u64 title_id, std::stop_token stop_loading,                         const VideoCore::DiskResourceLoadCallback& callback);      /// Gets the current specified shader stage program diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index ac78d344c..9c28498e8 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -96,7 +96,7 @@ public:      // etc).      template <typename... Args>      void AddLine(std::string_view text, Args&&... args) { -        AddExpression(fmt::format(text, std::forward<Args>(args)...)); +        AddExpression(fmt::format(fmt::runtime(text), std::forward<Args>(args)...));          AddNewLine();      } diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index 6dbb6bfba..2e67922a6 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -12,12 +12,15 @@  #include <glad/glad.h>  #include "common/common_types.h" +#include "common/literals.h"  #include "video_core/renderer_opengl/gl_resource_manager.h"  namespace OpenGL { +using namespace Common::Literals; +  class StreamBuffer { -    static constexpr size_t STREAM_BUFFER_SIZE = 64 * 1024 * 1024; +    static constexpr size_t STREAM_BUFFER_SIZE = 64_MiB;      static constexpr size_t NUM_SYNCS = 16;      static constexpr size_t REGION_SIZE = STREAM_BUFFER_SIZE / NUM_SYNCS;      static constexpr size_t MAX_ALIGNMENT = 256; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index ffe9edc1b..23948feed 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -9,6 +9,8 @@  #include <glad/glad.h> +#include "common/settings.h" +  #include "video_core/renderer_opengl/gl_device.h"  #include "video_core/renderer_opengl/gl_shader_manager.h"  #include "video_core/renderer_opengl/gl_state_tracker.h" @@ -307,7 +309,9 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4  [[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,                                      const VideoCommon::ImageInfo& info) { -    return !runtime.HasNativeASTC() && IsPixelFormatASTC(info.format); +    if (IsPixelFormatASTC(info.format)) { +        return !runtime.HasNativeASTC() && Settings::values.accelerate_astc.GetValue(); +    }      // Disable other accelerated uploads for now as they don't implement swizzled uploads      return false;      switch (info.type) { @@ -733,6 +737,8 @@ Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_,      }  } +Image::~Image() = default; +  void Image::UploadMemory(const ImageBufferMap& map,                           std::span<const VideoCommon::BufferImageCopy> copies) {      glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer); diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index df8be12ff..25fe61566 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -143,6 +143,14 @@ public:      explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,                     VAddr cpu_addr); +    ~Image(); + +    Image(const Image&) = delete; +    Image& operator=(const Image&) = delete; + +    Image(Image&&) = default; +    Image& operator=(Image&&) = default; +      void UploadMemory(const ImageBufferMap& map,                        std::span<const VideoCommon::BufferImageCopy> copies); @@ -235,6 +243,7 @@ struct TextureCacheParams {      static constexpr bool ENABLE_VALIDATION = true;      static constexpr bool FRAMEBUFFER_BLITS = true;      static constexpr bool HAS_EMULATED_COPIES = true; +    static constexpr bool HAS_DEVICE_MEMORY_INFO = false;      using Runtime = OpenGL::TextureCacheRuntime;      using Image = OpenGL::Image; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index cc19a110f..0b66f8332 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -70,6 +70,10 @@ public:          return &rasterizer;      } +    [[nodiscard]] std::string GetDeviceVendor() const override { +        return device.GetVendorName(); +    } +  private:      /// Initializes the OpenGL state and creates persistent objects.      void InitOpenGLObjects(); diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 47fddcb6e..abaf1ee6a 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -69,7 +69,8 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)      swizzle_table_buffer.Create();      astc_buffer.Create();      glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0); -    glNamedBufferStorage(astc_buffer.handle, sizeof(ASTC_BUFFER_DATA), &ASTC_BUFFER_DATA, 0); +    glNamedBufferStorage(astc_buffer.handle, sizeof(ASTC_ENCODINGS_VALUES), &ASTC_ENCODINGS_VALUES, +                         0);  }  UtilShaders::~UtilShaders() = default; @@ -79,12 +80,6 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,      static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;      static constexpr GLuint BINDING_INPUT_BUFFER = 1;      static constexpr GLuint BINDING_ENC_BUFFER = 2; - -    static constexpr GLuint BINDING_6_TO_8_BUFFER = 3; -    static constexpr GLuint BINDING_7_TO_8_BUFFER = 4; -    static constexpr GLuint BINDING_8_TO_8_BUFFER = 5; -    static constexpr GLuint BINDING_BYTE_TO_16_BUFFER = 6; -      static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;      const Extent2D tile_size{ @@ -93,21 +88,7 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,      };      program_manager.BindHostCompute(astc_decoder_program.handle);      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); -    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_ENC_BUFFER, astc_buffer.handle, -                      offsetof(AstcBufferData, encoding_values), -                      sizeof(AstcBufferData::encoding_values)); -    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_6_TO_8_BUFFER, astc_buffer.handle, -                      offsetof(AstcBufferData, replicate_6_to_8), -                      sizeof(AstcBufferData::replicate_6_to_8)); -    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_7_TO_8_BUFFER, astc_buffer.handle, -                      offsetof(AstcBufferData, replicate_7_to_8), -                      sizeof(AstcBufferData::replicate_7_to_8)); -    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_8_TO_8_BUFFER, astc_buffer.handle, -                      offsetof(AstcBufferData, replicate_8_to_8), -                      sizeof(AstcBufferData::replicate_8_to_8)); -    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_BYTE_TO_16_BUFFER, astc_buffer.handle, -                      offsetof(AstcBufferData, replicate_byte_to_16), -                      sizeof(AstcBufferData::replicate_byte_to_16)); +    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_ENC_BUFFER, astc_buffer.handle);      glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);      glUniform2ui(1, tile_size.width, tile_size.height); @@ -137,6 +118,12 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,          glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers);      } +    // Precautionary barrier to ensure the compute shader is done decoding prior to texture access. +    // GL_TEXTURE_FETCH_BARRIER_BIT and GL_SHADER_IMAGE_ACCESS_BARRIER_BIT are used in a separate +    // glMemoryBarrier call by the texture cache runtime +    glMemoryBarrier(GL_UNIFORM_BARRIER_BIT | GL_COMMAND_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT | +                    GL_TEXTURE_UPDATE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | +                    GL_SHADER_STORAGE_BARRIER_BIT | GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);      program_manager.RestoreGuestCompute();  } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 72071316c..d7d17e110 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -47,6 +47,10 @@ public:          return &rasterizer;      } +    [[nodiscard]] std::string GetDeviceVendor() const override { +        return device.GetDriverName(); +    } +  private:      void Report() const; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 8cb65e588..0df4e1a1c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -55,8 +55,9 @@ size_t BytesPerIndex(VkIndexType index_type) {  template <typename T>  std::array<T, 6> MakeQuadIndices(u32 quad, u32 first) {      std::array<T, 6> indices{0, 1, 2, 0, 2, 3}; -    std::ranges::transform(indices, indices.begin(), -                           [quad, first](u32 index) { return first + index + quad * 4; }); +    for (T& index : indices) { +        index = static_cast<T>(first + index + quad * 4); +    }      return indices;  }  } // Anonymous namespace diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index e11406e58..205cd3b05 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -30,19 +30,16 @@  namespace Vulkan {  using Tegra::Texture::SWIZZLE_TABLE; -using Tegra::Texture::ASTC::EncodingsValues; +using Tegra::Texture::ASTC::ASTC_ENCODINGS_VALUES;  using namespace Tegra::Texture::ASTC;  namespace {  constexpr u32 ASTC_BINDING_INPUT_BUFFER = 0;  constexpr u32 ASTC_BINDING_ENC_BUFFER = 1; -constexpr u32 ASTC_BINDING_6_TO_8_BUFFER = 2; -constexpr u32 ASTC_BINDING_7_TO_8_BUFFER = 3; -constexpr u32 ASTC_BINDING_8_TO_8_BUFFER = 4; -constexpr u32 ASTC_BINDING_BYTE_TO_16_BUFFER = 5; -constexpr u32 ASTC_BINDING_SWIZZLE_BUFFER = 6; -constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 7; +constexpr u32 ASTC_BINDING_SWIZZLE_BUFFER = 2; +constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 3; +constexpr size_t ASTC_NUM_BINDINGS = 4;  VkPushConstantRange BuildComputePushConstantRange(std::size_t size) {      return { @@ -71,7 +68,7 @@ std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBinding      }};  } -std::array<VkDescriptorSetLayoutBinding, 8> BuildASTCDescriptorSetBindings() { +std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> BuildASTCDescriptorSetBindings() {      return {{          {              .binding = ASTC_BINDING_INPUT_BUFFER, @@ -88,34 +85,6 @@ std::array<VkDescriptorSetLayoutBinding, 8> BuildASTCDescriptorSetBindings() {              .pImmutableSamplers = nullptr,          },          { -            .binding = ASTC_BINDING_6_TO_8_BUFFER, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .descriptorCount = 1, -            .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, -            .pImmutableSamplers = nullptr, -        }, -        { -            .binding = ASTC_BINDING_7_TO_8_BUFFER, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .descriptorCount = 1, -            .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, -            .pImmutableSamplers = nullptr, -        }, -        { -            .binding = ASTC_BINDING_8_TO_8_BUFFER, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .descriptorCount = 1, -            .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, -            .pImmutableSamplers = nullptr, -        }, -        { -            .binding = ASTC_BINDING_BYTE_TO_16_BUFFER, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .descriptorCount = 1, -            .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, -            .pImmutableSamplers = nullptr, -        }, -        {              .binding = ASTC_BINDING_SWIZZLE_BUFFER,              .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,              .descriptorCount = 1, @@ -143,7 +112,8 @@ VkDescriptorUpdateTemplateEntryKHR BuildInputOutputDescriptorUpdateTemplate() {      };  } -std::array<VkDescriptorUpdateTemplateEntryKHR, 8> BuildASTCPassDescriptorUpdateTemplateEntry() { +std::array<VkDescriptorUpdateTemplateEntryKHR, ASTC_NUM_BINDINGS> +BuildASTCPassDescriptorUpdateTemplateEntry() {      return {{          {              .dstBinding = ASTC_BINDING_INPUT_BUFFER, @@ -162,38 +132,6 @@ std::array<VkDescriptorUpdateTemplateEntryKHR, 8> BuildASTCPassDescriptorUpdateT              .stride = sizeof(DescriptorUpdateEntry),          },          { -            .dstBinding = ASTC_BINDING_6_TO_8_BUFFER, -            .dstArrayElement = 0, -            .descriptorCount = 1, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .offset = ASTC_BINDING_6_TO_8_BUFFER * sizeof(DescriptorUpdateEntry), -            .stride = sizeof(DescriptorUpdateEntry), -        }, -        { -            .dstBinding = ASTC_BINDING_7_TO_8_BUFFER, -            .dstArrayElement = 0, -            .descriptorCount = 1, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .offset = ASTC_BINDING_7_TO_8_BUFFER * sizeof(DescriptorUpdateEntry), -            .stride = sizeof(DescriptorUpdateEntry), -        }, -        { -            .dstBinding = ASTC_BINDING_8_TO_8_BUFFER, -            .dstArrayElement = 0, -            .descriptorCount = 1, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .offset = ASTC_BINDING_8_TO_8_BUFFER * sizeof(DescriptorUpdateEntry), -            .stride = sizeof(DescriptorUpdateEntry), -        }, -        { -            .dstBinding = ASTC_BINDING_BYTE_TO_16_BUFFER, -            .dstArrayElement = 0, -            .descriptorCount = 1, -            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, -            .offset = ASTC_BINDING_BYTE_TO_16_BUFFER * sizeof(DescriptorUpdateEntry), -            .stride = sizeof(DescriptorUpdateEntry), -        }, -        {              .dstBinding = ASTC_BINDING_SWIZZLE_BUFFER,              .dstArrayElement = 0,              .descriptorCount = 1, @@ -222,15 +160,6 @@ struct AstcPushConstants {      u32 block_height_mask;  }; -struct AstcBufferData { -    decltype(SWIZZLE_TABLE) swizzle_table_buffer = SWIZZLE_TABLE; -    decltype(EncodingsValues) encoding_values = EncodingsValues; -    decltype(REPLICATE_6_BIT_TO_8_TABLE) replicate_6_to_8 = REPLICATE_6_BIT_TO_8_TABLE; -    decltype(REPLICATE_7_BIT_TO_8_TABLE) replicate_7_to_8 = REPLICATE_7_BIT_TO_8_TABLE; -    decltype(REPLICATE_8_BIT_TO_8_TABLE) replicate_8_to_8 = REPLICATE_8_BIT_TO_8_TABLE; -    decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE; -} constexpr ASTC_BUFFER_DATA; -  } // Anonymous namespace  VKComputePass::VKComputePass(const Device& device, VKDescriptorPool& descriptor_pool, @@ -423,7 +352,7 @@ ASTCDecoderPass::ASTCDecoderPass(const Device& device_, VKScheduler& scheduler_,  ASTCDecoderPass::~ASTCDecoderPass() = default;  void ASTCDecoderPass::MakeDataBuffer() { -    constexpr size_t TOTAL_BUFFER_SIZE = sizeof(ASTC_BUFFER_DATA) + sizeof(SWIZZLE_TABLE); +    constexpr size_t TOTAL_BUFFER_SIZE = sizeof(ASTC_ENCODINGS_VALUES) + sizeof(SWIZZLE_TABLE);      data_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{          .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,          .pNext = nullptr, @@ -437,9 +366,10 @@ void ASTCDecoderPass::MakeDataBuffer() {      data_buffer_commit = memory_allocator.Commit(data_buffer, MemoryUsage::Upload);      const auto staging_ref = staging_buffer_pool.Request(TOTAL_BUFFER_SIZE, MemoryUsage::Upload); -    std::memcpy(staging_ref.mapped_span.data(), &ASTC_BUFFER_DATA, sizeof(ASTC_BUFFER_DATA)); +    std::memcpy(staging_ref.mapped_span.data(), &ASTC_ENCODINGS_VALUES, +                sizeof(ASTC_ENCODINGS_VALUES));      // Tack on the swizzle table at the end of the buffer -    std::memcpy(staging_ref.mapped_span.data() + sizeof(ASTC_BUFFER_DATA), &SWIZZLE_TABLE, +    std::memcpy(staging_ref.mapped_span.data() + sizeof(ASTC_ENCODINGS_VALUES), &SWIZZLE_TABLE,                  sizeof(SWIZZLE_TABLE));      scheduler.Record([src = staging_ref.buffer, offset = staging_ref.offset, dst = *data_buffer, @@ -509,18 +439,8 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,          update_descriptor_queue.Acquire();          update_descriptor_queue.AddBuffer(map.buffer, input_offset,                                            image.guest_size_bytes - swizzle.buffer_offset); -        update_descriptor_queue.AddBuffer(*data_buffer, offsetof(AstcBufferData, encoding_values), -                                          sizeof(AstcBufferData::encoding_values)); -        update_descriptor_queue.AddBuffer(*data_buffer, offsetof(AstcBufferData, replicate_6_to_8), -                                          sizeof(AstcBufferData::replicate_6_to_8)); -        update_descriptor_queue.AddBuffer(*data_buffer, offsetof(AstcBufferData, replicate_7_to_8), -                                          sizeof(AstcBufferData::replicate_7_to_8)); -        update_descriptor_queue.AddBuffer(*data_buffer, offsetof(AstcBufferData, replicate_8_to_8), -                                          sizeof(AstcBufferData::replicate_8_to_8)); -        update_descriptor_queue.AddBuffer(*data_buffer, -                                          offsetof(AstcBufferData, replicate_byte_to_16), -                                          sizeof(AstcBufferData::replicate_byte_to_16)); -        update_descriptor_queue.AddBuffer(*data_buffer, sizeof(AstcBufferData), +        update_descriptor_queue.AddBuffer(*data_buffer, 0, sizeof(ASTC_ENCODINGS_VALUES)); +        update_descriptor_queue.AddBuffer(*data_buffer, sizeof(ASTC_ENCODINGS_VALUES),                                            sizeof(SWIZZLE_TABLE));          update_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); @@ -569,6 +489,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,          cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,                                 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, image_barrier);      }); +    scheduler.Finish();  }  } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index db78ce3d9..6852c11b0 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -2,8 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include <atomic> -#include <chrono> +#include <thread>  #include "common/settings.h"  #include "video_core/renderer_vulkan/vk_master_semaphore.h" @@ -12,8 +11,6 @@  namespace Vulkan { -using namespace std::chrono_literals; -  MasterSemaphore::MasterSemaphore(const Device& device) {      static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{          .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, @@ -34,9 +31,9 @@ MasterSemaphore::MasterSemaphore(const Device& device) {      // Validation layers have a bug where they fail to track resource usage when using timeline      // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have      // a separate thread waiting for each timeline semaphore value. -    debug_thread = std::thread([this] { +    debug_thread = std::jthread([this](std::stop_token stop_token) {          u64 counter = 0; -        while (!shutdown) { +        while (!stop_token.stop_requested()) {              if (semaphore.Wait(counter, 10'000'000)) {                  ++counter;              } @@ -44,13 +41,6 @@ MasterSemaphore::MasterSemaphore(const Device& device) {      });  } -MasterSemaphore::~MasterSemaphore() { -    shutdown = true; - -    // This thread might not be started -    if (debug_thread.joinable()) { -        debug_thread.join(); -    } -} +MasterSemaphore::~MasterSemaphore() = default;  } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h index 4b6d64daa..ee3cd35d0 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.h +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h @@ -65,11 +65,10 @@ public:      }  private: -    vk::Semaphore semaphore;           ///< Timeline semaphore. -    std::atomic<u64> gpu_tick{0};      ///< Current known GPU tick. -    std::atomic<u64> current_tick{1};  ///< Current logical tick. -    std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed. -    std::thread debug_thread;          ///< Debug thread to workaround validation layer bugs. +    vk::Semaphore semaphore;          ///< Timeline semaphore. +    std::atomic<u64> gpu_tick{0};     ///< Current known GPU tick. +    std::atomic<u64> current_tick{1}; ///< Current logical tick. +    std::jthread debug_thread;        ///< Debug thread to workaround validation layer bugs.  };  } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 7a1232497..0412b5234 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -12,6 +12,7 @@  #include "common/assert.h"  #include "common/bit_util.h"  #include "common/common_types.h" +#include "common/literals.h"  #include "video_core/renderer_vulkan/vk_scheduler.h"  #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"  #include "video_core/vulkan_common/vulkan_device.h" @@ -19,12 +20,15 @@  namespace Vulkan {  namespace { + +using namespace Common::Literals; +  // Maximum potential alignment of a Vulkan buffer  constexpr VkDeviceSize MAX_ALIGNMENT = 256;  // Maximum size to put elements in the stream buffer -constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8 * 1024 * 1024; +constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;  // Stream buffer size in bytes -constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128 * 1024 * 1024; +constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;  constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;  constexpr VkMemoryPropertyFlags HOST_FLAGS = diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index a09fe084e..7b4875d0e 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -10,6 +10,7 @@  #include "common/alignment.h"  #include "common/assert.h" +#include "common/literals.h"  #include "video_core/renderer_vulkan/vk_scheduler.h"  #include "video_core/renderer_vulkan/vk_stream_buffer.h"  #include "video_core/vulkan_common/vulkan_device.h" @@ -19,6 +20,8 @@ namespace Vulkan {  namespace { +using namespace Common::Literals; +  constexpr VkBufferUsageFlags BUFFER_USAGE =      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |      VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -26,7 +29,7 @@ constexpr VkBufferUsageFlags BUFFER_USAGE =  constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;  constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; -constexpr u64 PREFERRED_STREAM_BUFFER_SIZE = 256 * 1024 * 1024; +constexpr u64 PREFERRED_STREAM_BUFFER_SIZE = 256_MiB;  /// Find a memory type with the passed requirements  std::optional<u32> FindMemoryType(const VkPhysicalDeviceMemoryProperties& properties, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index bdd0ce8bc..a2ab4d1ee 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -8,6 +8,7 @@  #include <vector>  #include "common/bit_cast.h" +#include "common/settings.h"  #include "video_core/engines/fermi_2d.h"  #include "video_core/renderer_vulkan/blit_image.h" @@ -817,6 +818,10 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,      });  } +u64 TextureCacheRuntime::GetDeviceLocalMemory() const { +    return device.GetDeviceLocalMemory(); +} +  Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_,               VAddr cpu_addr_)      : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler}, @@ -828,7 +833,11 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_          commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);      }      if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) { -        flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; +        if (Settings::values.accelerate_astc.GetValue()) { +            flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; +        } else { +            flags |= VideoCommon::ImageFlagBits::Converted; +        }      }      if (runtime.device.HasDebuggingToolAttached()) {          if (image) { @@ -871,6 +880,8 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_      }  } +Image::~Image() = default; +  void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {      // TODO: Move this to another API      scheduler->RequestOutsideRenderPassOperationContext(); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 4a57d378b..172bcdf98 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -97,6 +97,8 @@ struct TextureCacheRuntime {          // All known Vulkan drivers can natively handle BGR textures          return true;      } + +    u64 GetDeviceLocalMemory() const;  };  class Image : public VideoCommon::ImageBase { @@ -104,6 +106,14 @@ public:      explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,                     VAddr cpu_addr); +    ~Image(); + +    Image(const Image&) = delete; +    Image& operator=(const Image&) = delete; + +    Image(Image&&) = default; +    Image& operator=(Image&&) = default; +      void UploadMemory(const StagingBufferRef& map,                        std::span<const VideoCommon::BufferImageCopy> copies); @@ -257,6 +267,7 @@ struct TextureCacheParams {      static constexpr bool ENABLE_VALIDATION = true;      static constexpr bool FRAMEBUFFER_BLITS = false;      static constexpr bool HAS_EMULATED_COPIES = false; +    static constexpr bool HAS_DEVICE_MEMORY_INFO = true;      using Runtime = Vulkan::TextureCacheRuntime;      using Image = Vulkan::Image; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 6308aef94..eb1746265 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -283,4 +283,11 @@ std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {      return {DefaultBlockWidth(format), DefaultBlockHeight(format)};  } +u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format) { +    constexpr u64 RGBA8_PIXEL_SIZE = 4; +    const u64 base_block_size = static_cast<u64>(DefaultBlockWidth(format)) * +                                static_cast<u64>(DefaultBlockHeight(format)) * RGBA8_PIXEL_SIZE; +    return (base_size * base_block_size) / BytesPerBlock(format); +} +  } // namespace VideoCore::Surface diff --git a/src/video_core/surface.h b/src/video_core/surface.h index c40ab89d0..1503db81f 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -462,4 +462,6 @@ bool IsPixelFormatSRGB(PixelFormat format);  std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); +u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format); +  } // namespace VideoCore::Surface diff --git a/src/video_core/texture_cache/image_base.cpp b/src/video_core/texture_cache/image_base.cpp index 9914926b3..ad69d32d1 100644 --- a/src/video_core/texture_cache/image_base.cpp +++ b/src/video_core/texture_cache/image_base.cpp @@ -113,6 +113,43 @@ void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_vie      image_view_ids.push_back(image_view_id);  } +bool ImageBase::IsSafeDownload() const noexcept { +    // Skip images that were not modified from the GPU +    if (False(flags & ImageFlagBits::GpuModified)) { +        return false; +    } +    // Skip images that .are. modified from the CPU +    // We don't want to write sensitive data from the guest +    if (True(flags & ImageFlagBits::CpuModified)) { +        return false; +    } +    if (info.num_samples > 1) { +        LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); +        return false; +    } +    return true; +} + +void ImageBase::CheckBadOverlapState() { +    if (False(flags & ImageFlagBits::BadOverlap)) { +        return; +    } +    if (!overlapping_images.empty()) { +        return; +    } +    flags &= ~ImageFlagBits::BadOverlap; +} + +void ImageBase::CheckAliasState() { +    if (False(flags & ImageFlagBits::Alias)) { +        return; +    } +    if (!aliased_images.empty()) { +        return; +    } +    flags &= ~ImageFlagBits::Alias; +} +  void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) {      static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format;      ASSERT(lhs.info.type == rhs.info.type); diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h index b7f3b7e43..e326cab71 100644 --- a/src/video_core/texture_cache/image_base.h +++ b/src/video_core/texture_cache/image_base.h @@ -25,6 +25,12 @@ enum class ImageFlagBits : u32 {      Strong = 1 << 5,      ///< Exists in the image table, the dimensions are can be trusted      Registered = 1 << 6,  ///< True when the image is registered      Picked = 1 << 7,      ///< Temporary flag to mark the image as picked + +    // Garbage Collection Flags +    BadOverlap = 1 << 8, ///< This image overlaps other but doesn't fit, has higher +                         ///< garbage collection priority +    Alias = 1 << 9,      ///< This image has aliases and has priority on garbage +                         ///< collection  };  DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -44,11 +50,16 @@ struct ImageBase {      void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id); +    [[nodiscard]] bool IsSafeDownload() const noexcept; +      [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {          const VAddr overlap_end = overlap_cpu_addr + overlap_size;          return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;      } +    void CheckBadOverlapState(); +    void CheckAliasState(); +      ImageInfo info;      u32 guest_size_bytes = 0; @@ -72,6 +83,7 @@ struct ImageBase {      std::vector<SubresourceBase> slice_subresources;      std::vector<AliasedImage> aliased_images; +    std::vector<ImageId> overlapping_images;  };  struct ImageAllocBase { diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h index eae3be6ea..6180b8c0e 100644 --- a/src/video_core/texture_cache/slot_vector.h +++ b/src/video_core/texture_cache/slot_vector.h @@ -5,6 +5,7 @@  #pragma once  #include <array> +#include <bit>  #include <concepts>  #include <numeric>  #include <type_traits> @@ -32,6 +33,60 @@ template <class T>  requires std::is_nothrow_move_assignable_v<T>&&      std::is_nothrow_move_constructible_v<T> class SlotVector {  public: +    class Iterator { +        friend SlotVector<T>; + +    public: +        constexpr Iterator() = default; + +        Iterator& operator++() noexcept { +            const u64* const bitset = slot_vector->stored_bitset.data(); +            const u32 size = static_cast<u32>(slot_vector->stored_bitset.size()) * 64; +            if (id.index < size) { +                do { +                    ++id.index; +                } while (id.index < size && !IsValid(bitset)); +                if (id.index == size) { +                    id.index = SlotId::INVALID_INDEX; +                } +            } +            return *this; +        } + +        Iterator operator++(int) noexcept { +            const Iterator copy{*this}; +            ++*this; +            return copy; +        } + +        bool operator==(const Iterator& other) const noexcept { +            return id.index == other.id.index; +        } + +        bool operator!=(const Iterator& other) const noexcept { +            return id.index != other.id.index; +        } + +        std::pair<SlotId, T*> operator*() const noexcept { +            return {id, std::addressof((*slot_vector)[id])}; +        } + +        T* operator->() const noexcept { +            return std::addressof((*slot_vector)[id]); +        } + +    private: +        Iterator(SlotVector<T>* slot_vector_, SlotId id_) noexcept +            : slot_vector{slot_vector_}, id{id_} {} + +        bool IsValid(const u64* bitset) const noexcept { +            return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0; +        } + +        SlotVector<T>* slot_vector; +        SlotId id; +    }; +      ~SlotVector() noexcept {          size_t index = 0;          for (u64 bits : stored_bitset) { @@ -70,6 +125,20 @@ public:          ResetStorageBit(id.index);      } +    [[nodiscard]] Iterator begin() noexcept { +        const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; }); +        if (it == stored_bitset.end()) { +            return end(); +        } +        const u32 word_index = static_cast<u32>(std::distance(it, stored_bitset.begin())); +        const SlotId first_id{word_index * 64 + static_cast<u32>(std::countr_zero(*it))}; +        return Iterator(this, first_id); +    } + +    [[nodiscard]] Iterator end() noexcept { +        return Iterator(this, SlotId{SlotId::INVALID_INDEX}); +    } +  private:      struct NonTrivialDummy {          NonTrivialDummy() noexcept {} @@ -140,7 +209,6 @@ private:      Entry* values = nullptr;      size_t values_capacity = 0; -    size_t values_size = 0;      std::vector<u64> stored_bitset;      std::vector<u32> free_list; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 59b7c678b..c7cfd02b6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -19,9 +19,10 @@  #include <boost/container/small_vector.hpp>  #include "common/alignment.h" -#include "common/common_funcs.h"  #include "common/common_types.h" +#include "common/literals.h"  #include "common/logging/log.h" +#include "common/settings.h"  #include "video_core/compatible_formats.h"  #include "video_core/delayed_destruction_ring.h"  #include "video_core/dirty_flags.h" @@ -57,6 +58,7 @@ using VideoCore::Surface::PixelFormat;  using VideoCore::Surface::PixelFormatFromDepthFormat;  using VideoCore::Surface::PixelFormatFromRenderTargetFormat;  using VideoCore::Surface::SurfaceType; +using namespace Common::Literals;  template <class P>  class TextureCache { @@ -69,12 +71,17 @@ class TextureCache {      static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS;      /// True when some copies have to be emulated      static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; +    /// True when the API can provide info about the memory of the device. +    static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;      /// Image view ID for null descriptors      static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0};      /// Sampler ID for bugged sampler ids      static constexpr SamplerId NULL_SAMPLER_ID{0}; +    static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB; +    static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB; +      using Runtime = typename P::Runtime;      using Image = typename P::Image;      using ImageAlloc = typename P::ImageAlloc; @@ -197,6 +204,9 @@ private:          }      } +    /// Runs the Garbage Collector. +    void RunGarbageCollector(); +      /// Fills image_view_ids in the image views in indices      void FillImageViews(DescriptorTable<TICEntry>& table,                          std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices, @@ -333,6 +343,10 @@ private:      std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table;      bool has_deleted_images = false; +    u64 total_used_memory = 0; +    u64 minimum_memory; +    u64 expected_memory; +    u64 critical_memory;      SlotVector<Image> slot_images;      SlotVector<ImageView> slot_image_views; @@ -353,6 +367,7 @@ private:      u64 modification_tick = 0;      u64 frame_tick = 0; +    typename SlotVector<Image>::Iterator deletion_iterator;  };  template <class P> @@ -373,11 +388,94 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&      // This way the null resource becomes a compile time constant      void(slot_image_views.insert(runtime, NullImageParams{}));      void(slot_samplers.insert(runtime, sampler_descriptor)); + +    deletion_iterator = slot_images.begin(); + +    if constexpr (HAS_DEVICE_MEMORY_INFO) { +        const auto device_memory = runtime.GetDeviceLocalMemory(); +        const u64 possible_expected_memory = (device_memory * 3) / 10; +        const u64 possible_critical_memory = (device_memory * 6) / 10; +        expected_memory = std::max(possible_expected_memory, DEFAULT_EXPECTED_MEMORY); +        critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY); +        minimum_memory = 0; +    } else { +        // on OGL we can be more conservatives as the driver takes care. +        expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB; +        critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB; +        minimum_memory = expected_memory; +    } +} + +template <class P> +void TextureCache<P>::RunGarbageCollector() { +    const bool high_priority_mode = total_used_memory >= expected_memory; +    const bool aggressive_mode = total_used_memory >= critical_memory; +    const u64 ticks_to_destroy = high_priority_mode ? 60 : 100; +    int num_iterations = aggressive_mode ? 256 : (high_priority_mode ? 128 : 64); +    for (; num_iterations > 0; --num_iterations) { +        if (deletion_iterator == slot_images.end()) { +            deletion_iterator = slot_images.begin(); +            if (deletion_iterator == slot_images.end()) { +                break; +            } +        } +        auto [image_id, image_tmp] = *deletion_iterator; +        Image* image = image_tmp; // fix clang error. +        const bool is_alias = True(image->flags & ImageFlagBits::Alias); +        const bool is_bad_overlap = True(image->flags & ImageFlagBits::BadOverlap); +        const bool must_download = image->IsSafeDownload(); +        bool should_care = is_bad_overlap || is_alias || (high_priority_mode && !must_download); +        const u64 ticks_needed = +            is_bad_overlap +                ? ticks_to_destroy >> 4 +                : ((should_care && aggressive_mode) ? ticks_to_destroy >> 1 : ticks_to_destroy); +        should_care |= aggressive_mode; +        if (should_care && image->frame_tick + ticks_needed < frame_tick) { +            if (is_bad_overlap) { +                const bool overlap_check = std::ranges::all_of( +                    image->overlapping_images, [&, image](const ImageId& overlap_id) { +                        auto& overlap = slot_images[overlap_id]; +                        return overlap.frame_tick >= image->frame_tick; +                    }); +                if (!overlap_check) { +                    ++deletion_iterator; +                    continue; +                } +            } +            if (!is_bad_overlap && must_download) { +                const bool alias_check = std::ranges::none_of( +                    image->aliased_images, [&, image](const AliasedImage& alias) { +                        auto& alias_image = slot_images[alias.id]; +                        return (alias_image.frame_tick < image->frame_tick) || +                               (alias_image.modification_tick < image->modification_tick); +                    }); + +                if (alias_check) { +                    auto map = runtime.DownloadStagingBuffer(image->unswizzled_size_bytes); +                    const auto copies = FullDownloadCopies(image->info); +                    image->DownloadMemory(map, copies); +                    runtime.Finish(); +                    SwizzleImage(gpu_memory, image->gpu_addr, image->info, copies, map.mapped_span); +                } +            } +            if (True(image->flags & ImageFlagBits::Tracked)) { +                UntrackImage(*image); +            } +            UnregisterImage(image_id); +            DeleteImage(image_id); +            if (is_bad_overlap) { +                ++num_iterations; +            } +        } +        ++deletion_iterator; +    }  }  template <class P>  void TextureCache<P>::TickFrame() { -    // Tick sentenced resources in this order to ensure they are destroyed in the right order +    if (Settings::values.use_caches_gc.GetValue() && total_used_memory > minimum_memory) { +        RunGarbageCollector(); +    }      sentenced_images.Tick();      sentenced_framebuffers.Tick();      sentenced_image_view.Tick(); @@ -568,17 +666,7 @@ template <class P>  void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {      std::vector<ImageId> images;      ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { -        // Skip images that were not modified from the GPU -        if (False(image.flags & ImageFlagBits::GpuModified)) { -            return; -        } -        // Skip images that .are. modified from the CPU -        // We don't want to write sensitive data from the guest -        if (True(image.flags & ImageFlagBits::CpuModified)) { -            return; -        } -        if (image.info.num_samples > 1) { -            LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); +        if (!image.IsSafeDownload()) {              return;          }          image.flags &= ~ImageFlagBits::GpuModified; @@ -967,6 +1055,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA      std::vector<ImageId> overlap_ids;      std::vector<ImageId> left_aliased_ids;      std::vector<ImageId> right_aliased_ids; +    std::vector<ImageId> bad_overlap_ids;      ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {          if (info.type != overlap.info.type) {              return; @@ -992,9 +1081,14 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA          const ImageBase new_image_base(new_info, gpu_addr, cpu_addr);          if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views, native_bgr)) {              left_aliased_ids.push_back(overlap_id); +            overlap.flags |= ImageFlagBits::Alias;          } else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options,                                   broken_views, native_bgr)) {              right_aliased_ids.push_back(overlap_id); +            overlap.flags |= ImageFlagBits::Alias; +        } else { +            bad_overlap_ids.push_back(overlap_id); +            overlap.flags |= ImageFlagBits::BadOverlap;          }      });      const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr); @@ -1022,10 +1116,18 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA      for (const ImageId aliased_id : right_aliased_ids) {          ImageBase& aliased = slot_images[aliased_id];          AddImageAlias(new_image_base, aliased, new_image_id, aliased_id); +        new_image.flags |= ImageFlagBits::Alias;      }      for (const ImageId aliased_id : left_aliased_ids) {          ImageBase& aliased = slot_images[aliased_id];          AddImageAlias(aliased, new_image_base, aliased_id, new_image_id); +        new_image.flags |= ImageFlagBits::Alias; +    } +    for (const ImageId aliased_id : bad_overlap_ids) { +        ImageBase& aliased = slot_images[aliased_id]; +        aliased.overlapping_images.push_back(new_image_id); +        new_image.overlapping_images.push_back(aliased_id); +        new_image.flags |= ImageFlagBits::BadOverlap;      }      RegisterImage(new_image_id);      return new_image_id; @@ -1195,6 +1297,13 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {      image.flags |= ImageFlagBits::Registered;      ForEachPage(image.cpu_addr, image.guest_size_bytes,                  [this, image_id](u64 page) { page_table[page].push_back(image_id); }); +    u64 tentative_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes); +    if ((IsPixelFormatASTC(image.info.format) && +         True(image.flags & ImageFlagBits::AcceleratedUpload)) || +        True(image.flags & ImageFlagBits::Converted)) { +        tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); +    } +    total_used_memory += Common::AlignUp(tentative_size, 1024);  }  template <class P> @@ -1203,6 +1312,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {      ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),                 "Trying to unregister an already registered image");      image.flags &= ~ImageFlagBits::Registered; +    image.flags &= ~ImageFlagBits::BadOverlap; +    u64 tentative_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes); +    if ((IsPixelFormatASTC(image.info.format) && +         True(image.flags & ImageFlagBits::AcceleratedUpload)) || +        True(image.flags & ImageFlagBits::Converted)) { +        tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); +    } +    total_used_memory -= Common::AlignUp(tentative_size, 1024);      ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {          const auto page_it = page_table.find(page);          if (page_it == page_table.end()) { @@ -1276,9 +1393,19 @@ void TextureCache<P>::DeleteImage(ImageId image_id) {              std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) {                  return other_alias.id == image_id;              }); +        other_image.CheckAliasState();          ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}",                     num_removed_aliases);      } +    for (const ImageId overlap_id : image.overlapping_images) { +        ImageBase& other_image = slot_images[overlap_id]; +        [[maybe_unused]] const size_t num_removed_overlaps = std::erase_if( +            other_image.overlapping_images, +            [image_id](const ImageId other_overlap_id) { return other_overlap_id == image_id; }); +        other_image.CheckBadOverlapState(); +        ASSERT_MSG(num_removed_overlaps == 1, "Invalid number of removed overlapps: {}", +                   num_removed_overlaps); +    }      for (const ImageViewId image_view_id : image_view_ids) {          sentenced_image_view.Push(std::move(slot_image_views[image_view_id]));          slot_image_views.erase(image_view_id); diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 906604a39..4efe042b6 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -47,6 +47,7 @@  #include "video_core/texture_cache/formatter.h"  #include "video_core/texture_cache/samples_helper.h"  #include "video_core/texture_cache/util.h" +#include "video_core/textures/astc.h"  #include "video_core/textures/decoders.h"  namespace VideoCommon { @@ -580,6 +581,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr      for (s32 layer = 0; layer < info.resources.layers; ++layer) {          const std::span<const u8> src = input.subspan(host_offset); +        gpu_memory.ReadBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes()); +          SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,                         num_tiles.depth, block.height, block.depth); @@ -884,8 +887,16 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8          ASSERT(copy.image_extent == mip_size);          ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width));          ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); -        DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, -                      output.subspan(output_offset)); +        if (IsPixelFormatASTC(info.format)) { +            ASSERT(copy.image_extent.depth == 1); +            Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset), +                                             copy.image_extent.width, copy.image_extent.height, +                                             copy.image_subresource.num_layers, tile_size.width, +                                             tile_size.height, output.subspan(output_offset)); +        } else { +            DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, +                          output.subspan(output_offset)); +        }          copy.buffer_offset = output_offset;          copy.buffer_row_length = mip_size.width;          copy.buffer_image_height = mip_size.height; @@ -1087,7 +1098,15 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const          return std::nullopt;      }      const ImageInfo& existing = image.info; -    if (False(options & RelaxedOptions::Format)) { +    if (True(options & RelaxedOptions::Format)) { +        // Format checking is relaxed, but we still have to check for matching bytes per block. +        // This avoids creating a view for blits on UE4 titles where formats with different bytes +        // per block are aliased. +        if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format)) { +            return std::nullopt; +        } +    } else { +        // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format          if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {              return std::nullopt;          } diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp new file mode 100644 index 000000000..7b756ba41 --- /dev/null +++ b/src/video_core/textures/astc.cpp @@ -0,0 +1,1579 @@ +// Copyright 2016 The University of North Carolina at Chapel Hill +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//    http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Please send all BUG REPORTS to <pavel@cs.unc.edu>. +// <http://gamma.cs.unc.edu/FasTC/> + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <span> +#include <vector> + +#include <boost/container/static_vector.hpp> + +#include "common/common_types.h" +#include "video_core/textures/astc.h" + +class InputBitStream { +public: +    constexpr explicit InputBitStream(std::span<const u8> data, size_t start_offset = 0) +        : cur_byte{data.data()}, total_bits{data.size()}, next_bit{start_offset % 8} {} + +    constexpr size_t GetBitsRead() const { +        return bits_read; +    } + +    constexpr bool ReadBit() { +        if (bits_read >= total_bits * 8) { +            return 0; +        } +        const bool bit = ((*cur_byte >> next_bit) & 1) != 0; +        ++next_bit; +        while (next_bit >= 8) { +            next_bit -= 8; +            ++cur_byte; +        } +        ++bits_read; +        return bit; +    } + +    constexpr u32 ReadBits(std::size_t nBits) { +        u32 ret = 0; +        for (std::size_t i = 0; i < nBits; ++i) { +            ret |= (ReadBit() & 1) << i; +        } +        return ret; +    } + +    template <std::size_t nBits> +    constexpr u32 ReadBits() { +        u32 ret = 0; +        for (std::size_t i = 0; i < nBits; ++i) { +            ret |= (ReadBit() & 1) << i; +        } +        return ret; +    } + +private: +    const u8* cur_byte; +    size_t total_bits = 0; +    size_t next_bit = 0; +    size_t bits_read = 0; +}; + +class OutputBitStream { +public: +    constexpr explicit OutputBitStream(u8* ptr, std::size_t bits = 0, std::size_t start_offset = 0) +        : cur_byte{ptr}, num_bits{bits}, next_bit{start_offset % 8} {} + +    constexpr std::size_t GetBitsWritten() const { +        return bits_written; +    } + +    constexpr void WriteBitsR(u32 val, u32 nBits) { +        for (u32 i = 0; i < nBits; i++) { +            WriteBit((val >> (nBits - i - 1)) & 1); +        } +    } + +    constexpr void WriteBits(u32 val, u32 nBits) { +        for (u32 i = 0; i < nBits; i++) { +            WriteBit((val >> i) & 1); +        } +    } + +private: +    constexpr void WriteBit(bool b) { +        if (bits_written >= num_bits) { +            return; +        } + +        const u32 mask = 1 << next_bit++; + +        // clear the bit +        *cur_byte &= static_cast<u8>(~mask); + +        // Write the bit, if necessary +        if (b) +            *cur_byte |= static_cast<u8>(mask); + +        // Next byte? +        if (next_bit >= 8) { +            cur_byte += 1; +            next_bit = 0; +        } +    } + +    u8* cur_byte; +    std::size_t num_bits; +    std::size_t bits_written = 0; +    std::size_t next_bit = 0; +}; + +template <typename IntType> +class Bits { +public: +    explicit Bits(const IntType& v) : m_Bits(v) {} + +    Bits(const Bits&) = delete; +    Bits& operator=(const Bits&) = delete; + +    u8 operator[](u32 bitPos) const { +        return static_cast<u8>((m_Bits >> bitPos) & 1); +    } + +    IntType operator()(u32 start, u32 end) const { +        if (start == end) { +            return (*this)[start]; +        } else if (start > end) { +            u32 t = start; +            start = end; +            end = t; +        } + +        u64 mask = (1 << (end - start + 1)) - 1; +        return (m_Bits >> start) & static_cast<IntType>(mask); +    } + +private: +    const IntType& m_Bits; +}; + +namespace Tegra::Texture::ASTC { +using IntegerEncodedVector = boost::container::static_vector< +    IntegerEncodedValue, 256, +    boost::container::static_vector_options< +        boost::container::inplace_alignment<alignof(IntegerEncodedValue)>, +        boost::container::throw_on_overflow<false>>::type>; + +static void DecodeTritBlock(InputBitStream& bits, IntegerEncodedVector& result, u32 nBitsPerValue) { +    // Implement the algorithm in section C.2.12 +    std::array<u32, 5> m; +    std::array<u32, 5> t; +    u32 T; + +    // Read the trit encoded block according to +    // table C.2.14 +    m[0] = bits.ReadBits(nBitsPerValue); +    T = bits.ReadBits<2>(); +    m[1] = bits.ReadBits(nBitsPerValue); +    T |= bits.ReadBits<2>() << 2; +    m[2] = bits.ReadBits(nBitsPerValue); +    T |= bits.ReadBit() << 4; +    m[3] = bits.ReadBits(nBitsPerValue); +    T |= bits.ReadBits<2>() << 5; +    m[4] = bits.ReadBits(nBitsPerValue); +    T |= bits.ReadBit() << 7; + +    u32 C = 0; + +    Bits<u32> Tb(T); +    if (Tb(2, 4) == 7) { +        C = (Tb(5, 7) << 2) | Tb(0, 1); +        t[4] = t[3] = 2; +    } else { +        C = Tb(0, 4); +        if (Tb(5, 6) == 3) { +            t[4] = 2; +            t[3] = Tb[7]; +        } else { +            t[4] = Tb[7]; +            t[3] = Tb(5, 6); +        } +    } + +    Bits<u32> Cb(C); +    if (Cb(0, 1) == 3) { +        t[2] = 2; +        t[1] = Cb[4]; +        t[0] = (Cb[3] << 1) | (Cb[2] & ~Cb[3]); +    } else if (Cb(2, 3) == 3) { +        t[2] = 2; +        t[1] = 2; +        t[0] = Cb(0, 1); +    } else { +        t[2] = Cb[4]; +        t[1] = Cb(2, 3); +        t[0] = (Cb[1] << 1) | (Cb[0] & ~Cb[1]); +    } + +    for (std::size_t i = 0; i < 5; ++i) { +        IntegerEncodedValue& val = result.emplace_back(IntegerEncoding::Trit, nBitsPerValue); +        val.bit_value = m[i]; +        val.trit_value = t[i]; +    } +} + +static void DecodeQuintBlock(InputBitStream& bits, IntegerEncodedVector& result, +                             u32 nBitsPerValue) { +    // Implement the algorithm in section C.2.12 +    u32 m[3]; +    u32 q[3]; +    u32 Q; + +    // Read the trit encoded block according to +    // table C.2.15 +    m[0] = bits.ReadBits(nBitsPerValue); +    Q = bits.ReadBits<3>(); +    m[1] = bits.ReadBits(nBitsPerValue); +    Q |= bits.ReadBits<2>() << 3; +    m[2] = bits.ReadBits(nBitsPerValue); +    Q |= bits.ReadBits<2>() << 5; + +    Bits<u32> Qb(Q); +    if (Qb(1, 2) == 3 && Qb(5, 6) == 0) { +        q[0] = q[1] = 4; +        q[2] = (Qb[0] << 2) | ((Qb[4] & ~Qb[0]) << 1) | (Qb[3] & ~Qb[0]); +    } else { +        u32 C = 0; +        if (Qb(1, 2) == 3) { +            q[2] = 4; +            C = (Qb(3, 4) << 3) | ((~Qb(5, 6) & 3) << 1) | Qb[0]; +        } else { +            q[2] = Qb(5, 6); +            C = Qb(0, 4); +        } + +        Bits<u32> Cb(C); +        if (Cb(0, 2) == 5) { +            q[1] = 4; +            q[0] = Cb(3, 4); +        } else { +            q[1] = Cb(3, 4); +            q[0] = Cb(0, 2); +        } +    } + +    for (std::size_t i = 0; i < 3; ++i) { +        IntegerEncodedValue& val = result.emplace_back(IntegerEncoding::Quint, nBitsPerValue); +        val.bit_value = m[i]; +        val.quint_value = q[i]; +    } +} + +// Fills result with the values that are encoded in the given +// bitstream. We must know beforehand what the maximum possible +// value is, and how many values we're decoding. +static void DecodeIntegerSequence(IntegerEncodedVector& result, InputBitStream& bits, u32 maxRange, +                                  u32 nValues) { +    // Determine encoding parameters +    IntegerEncodedValue val = ASTC_ENCODINGS_VALUES[maxRange]; + +    // Start decoding +    u32 nValsDecoded = 0; +    while (nValsDecoded < nValues) { +        switch (val.encoding) { +        case IntegerEncoding::Quint: +            DecodeQuintBlock(bits, result, val.num_bits); +            nValsDecoded += 3; +            break; + +        case IntegerEncoding::Trit: +            DecodeTritBlock(bits, result, val.num_bits); +            nValsDecoded += 5; +            break; + +        case IntegerEncoding::JustBits: +            val.bit_value = bits.ReadBits(val.num_bits); +            result.push_back(val); +            nValsDecoded++; +            break; +        } +    } +} + +struct TexelWeightParams { +    u32 m_Width = 0; +    u32 m_Height = 0; +    bool m_bDualPlane = false; +    u32 m_MaxWeight = 0; +    bool m_bError = false; +    bool m_bVoidExtentLDR = false; +    bool m_bVoidExtentHDR = false; + +    u32 GetPackedBitSize() const { +        // How many indices do we have? +        u32 nIdxs = m_Height * m_Width; +        if (m_bDualPlane) { +            nIdxs *= 2; +        } + +        return ASTC_ENCODINGS_VALUES[m_MaxWeight].GetBitLength(nIdxs); +    } + +    u32 GetNumWeightValues() const { +        u32 ret = m_Width * m_Height; +        if (m_bDualPlane) { +            ret *= 2; +        } +        return ret; +    } +}; + +static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) { +    TexelWeightParams params; + +    // Read the entire block mode all at once +    u16 modeBits = static_cast<u16>(strm.ReadBits<11>()); + +    // Does this match the void extent block mode? +    if ((modeBits & 0x01FF) == 0x1FC) { +        if (modeBits & 0x200) { +            params.m_bVoidExtentHDR = true; +        } else { +            params.m_bVoidExtentLDR = true; +        } + +        // Next two bits must be one. +        if (!(modeBits & 0x400) || !strm.ReadBit()) { +            params.m_bError = true; +        } + +        return params; +    } + +    // First check if the last four bits are zero +    if ((modeBits & 0xF) == 0) { +        params.m_bError = true; +        return params; +    } + +    // If the last two bits are zero, then if bits +    // [6-8] are all ones, this is also reserved. +    if ((modeBits & 0x3) == 0 && (modeBits & 0x1C0) == 0x1C0) { +        params.m_bError = true; +        return params; +    } + +    // Otherwise, there is no error... Figure out the layout +    // of the block mode. Layout is determined by a number +    // between 0 and 9 corresponding to table C.2.8 of the +    // ASTC spec. +    u32 layout = 0; + +    if ((modeBits & 0x1) || (modeBits & 0x2)) { +        // layout is in [0-4] +        if (modeBits & 0x8) { +            // layout is in [2-4] +            if (modeBits & 0x4) { +                // layout is in [3-4] +                if (modeBits & 0x100) { +                    layout = 4; +                } else { +                    layout = 3; +                } +            } else { +                layout = 2; +            } +        } else { +            // layout is in [0-1] +            if (modeBits & 0x4) { +                layout = 1; +            } else { +                layout = 0; +            } +        } +    } else { +        // layout is in [5-9] +        if (modeBits & 0x100) { +            // layout is in [7-9] +            if (modeBits & 0x80) { +                // layout is in [7-8] +                assert((modeBits & 0x40) == 0U); +                if (modeBits & 0x20) { +                    layout = 8; +                } else { +                    layout = 7; +                } +            } else { +                layout = 9; +            } +        } else { +            // layout is in [5-6] +            if (modeBits & 0x80) { +                layout = 6; +            } else { +                layout = 5; +            } +        } +    } + +    assert(layout < 10); + +    // Determine R +    u32 R = !!(modeBits & 0x10); +    if (layout < 5) { +        R |= (modeBits & 0x3) << 1; +    } else { +        R |= (modeBits & 0xC) >> 1; +    } +    assert(2 <= R && R <= 7); + +    // Determine width & height +    switch (layout) { +    case 0: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 7) & 0x3; +        params.m_Width = B + 4; +        params.m_Height = A + 2; +        break; +    } + +    case 1: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 7) & 0x3; +        params.m_Width = B + 8; +        params.m_Height = A + 2; +        break; +    } + +    case 2: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 7) & 0x3; +        params.m_Width = A + 2; +        params.m_Height = B + 8; +        break; +    } + +    case 3: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 7) & 0x1; +        params.m_Width = A + 2; +        params.m_Height = B + 6; +        break; +    } + +    case 4: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 7) & 0x1; +        params.m_Width = B + 2; +        params.m_Height = A + 2; +        break; +    } + +    case 5: { +        u32 A = (modeBits >> 5) & 0x3; +        params.m_Width = 12; +        params.m_Height = A + 2; +        break; +    } + +    case 6: { +        u32 A = (modeBits >> 5) & 0x3; +        params.m_Width = A + 2; +        params.m_Height = 12; +        break; +    } + +    case 7: { +        params.m_Width = 6; +        params.m_Height = 10; +        break; +    } + +    case 8: { +        params.m_Width = 10; +        params.m_Height = 6; +        break; +    } + +    case 9: { +        u32 A = (modeBits >> 5) & 0x3; +        u32 B = (modeBits >> 9) & 0x3; +        params.m_Width = A + 6; +        params.m_Height = B + 6; +        break; +    } + +    default: +        assert(false && "Don't know this layout..."); +        params.m_bError = true; +        break; +    } + +    // Determine whether or not we're using dual planes +    // and/or high precision layouts. +    bool D = (layout != 9) && (modeBits & 0x400); +    bool H = (layout != 9) && (modeBits & 0x200); + +    if (H) { +        const u32 maxWeights[6] = {9, 11, 15, 19, 23, 31}; +        params.m_MaxWeight = maxWeights[R - 2]; +    } else { +        const u32 maxWeights[6] = {1, 2, 3, 4, 5, 7}; +        params.m_MaxWeight = maxWeights[R - 2]; +    } + +    params.m_bDualPlane = D; + +    return params; +} + +static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth, +                              u32 blockHeight) { +    // Don't actually care about the void extent, just read the bits... +    for (s32 i = 0; i < 4; ++i) { +        strm.ReadBits<13>(); +    } + +    // Decode the RGBA components and renormalize them to the range [0, 255] +    u16 r = static_cast<u16>(strm.ReadBits<16>()); +    u16 g = static_cast<u16>(strm.ReadBits<16>()); +    u16 b = static_cast<u16>(strm.ReadBits<16>()); +    u16 a = static_cast<u16>(strm.ReadBits<16>()); + +    u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 | +               (static_cast<u32>(a) & 0xFF00) << 16; + +    for (u32 j = 0; j < blockHeight; j++) { +        for (u32 i = 0; i < blockWidth; i++) { +            outBuf[j * blockWidth + i] = rgba; +        } +    } +} + +static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { +    for (u32 j = 0; j < blockHeight; j++) { +        for (u32 i = 0; i < blockWidth; i++) { +            outBuf[j * blockWidth + i] = 0xFFFF00FF; +        } +    } +} + +static constexpr auto REPLICATE_BYTE_TO_16_TABLE = MakeReplicateTable<u32, 8, 16>(); +static constexpr u32 ReplicateByteTo16(std::size_t value) { +    return REPLICATE_BYTE_TO_16_TABLE[value]; +} + +static constexpr auto REPLICATE_BIT_TO_7_TABLE = MakeReplicateTable<u32, 1, 7>(); +static constexpr u32 ReplicateBitTo7(std::size_t value) { +    return REPLICATE_BIT_TO_7_TABLE[value]; +} + +static constexpr auto REPLICATE_BIT_TO_9_TABLE = MakeReplicateTable<u32, 1, 9>(); +static constexpr u32 ReplicateBitTo9(std::size_t value) { +    return REPLICATE_BIT_TO_9_TABLE[value]; +} + +static constexpr auto REPLICATE_1_BIT_TO_8_TABLE = MakeReplicateTable<u32, 1, 8>(); +static constexpr auto REPLICATE_2_BIT_TO_8_TABLE = MakeReplicateTable<u32, 2, 8>(); +static constexpr auto REPLICATE_3_BIT_TO_8_TABLE = MakeReplicateTable<u32, 3, 8>(); +static constexpr auto REPLICATE_4_BIT_TO_8_TABLE = MakeReplicateTable<u32, 4, 8>(); +static constexpr auto REPLICATE_5_BIT_TO_8_TABLE = MakeReplicateTable<u32, 5, 8>(); +/// Use a precompiled table with the most common usages, if it's not in the expected range, fallback +/// to the runtime implementation +static constexpr u32 FastReplicateTo8(u32 value, u32 num_bits) { +    switch (num_bits) { +    case 1: +        return REPLICATE_1_BIT_TO_8_TABLE[value]; +    case 2: +        return REPLICATE_2_BIT_TO_8_TABLE[value]; +    case 3: +        return REPLICATE_3_BIT_TO_8_TABLE[value]; +    case 4: +        return REPLICATE_4_BIT_TO_8_TABLE[value]; +    case 5: +        return REPLICATE_5_BIT_TO_8_TABLE[value]; +    case 6: +        return REPLICATE_6_BIT_TO_8_TABLE[value]; +    case 7: +        return REPLICATE_7_BIT_TO_8_TABLE[value]; +    case 8: +        return REPLICATE_8_BIT_TO_8_TABLE[value]; +    default: +        return Replicate(value, num_bits, 8); +    } +} + +static constexpr auto REPLICATE_1_BIT_TO_6_TABLE = MakeReplicateTable<u32, 1, 6>(); +static constexpr auto REPLICATE_2_BIT_TO_6_TABLE = MakeReplicateTable<u32, 2, 6>(); +static constexpr auto REPLICATE_3_BIT_TO_6_TABLE = MakeReplicateTable<u32, 3, 6>(); +static constexpr auto REPLICATE_4_BIT_TO_6_TABLE = MakeReplicateTable<u32, 4, 6>(); +static constexpr auto REPLICATE_5_BIT_TO_6_TABLE = MakeReplicateTable<u32, 5, 6>(); +static constexpr u32 FastReplicateTo6(u32 value, u32 num_bits) { +    switch (num_bits) { +    case 1: +        return REPLICATE_1_BIT_TO_6_TABLE[value]; +    case 2: +        return REPLICATE_2_BIT_TO_6_TABLE[value]; +    case 3: +        return REPLICATE_3_BIT_TO_6_TABLE[value]; +    case 4: +        return REPLICATE_4_BIT_TO_6_TABLE[value]; +    case 5: +        return REPLICATE_5_BIT_TO_6_TABLE[value]; +    default: +        return Replicate(value, num_bits, 6); +    } +} + +class Pixel { +protected: +    using ChannelType = s16; +    u8 m_BitDepth[4] = {8, 8, 8, 8}; +    s16 color[4] = {}; + +public: +    Pixel() = default; +    Pixel(u32 a, u32 r, u32 g, u32 b, u32 bitDepth = 8) +        : m_BitDepth{u8(bitDepth), u8(bitDepth), u8(bitDepth), u8(bitDepth)}, +          color{static_cast<ChannelType>(a), static_cast<ChannelType>(r), +                static_cast<ChannelType>(g), static_cast<ChannelType>(b)} {} + +    // Changes the depth of each pixel. This scales the values to +    // the appropriate bit depth by either truncating the least +    // significant bits when going from larger to smaller bit depth +    // or by repeating the most significant bits when going from +    // smaller to larger bit depths. +    void ChangeBitDepth() { +        for (u32 i = 0; i < 4; i++) { +            Component(i) = ChangeBitDepth(Component(i), m_BitDepth[i]); +            m_BitDepth[i] = 8; +        } +    } + +    template <typename IntType> +    static float ConvertChannelToFloat(IntType channel, u8 bitDepth) { +        float denominator = static_cast<float>((1 << bitDepth) - 1); +        return static_cast<float>(channel) / denominator; +    } + +    // Changes the bit depth of a single component. See the comment +    // above for how we do this. +    static ChannelType ChangeBitDepth(Pixel::ChannelType val, u8 oldDepth) { +        assert(oldDepth <= 8); + +        if (oldDepth == 8) { +            // Do nothing +            return val; +        } else if (oldDepth == 0) { +            return static_cast<ChannelType>((1 << 8) - 1); +        } else if (8 > oldDepth) { +            return static_cast<ChannelType>(FastReplicateTo8(static_cast<u32>(val), oldDepth)); +        } else { +            // oldDepth > newDepth +            const u8 bitsWasted = static_cast<u8>(oldDepth - 8); +            u16 v = static_cast<u16>(val); +            v = static_cast<u16>((v + (1 << (bitsWasted - 1))) >> bitsWasted); +            v = ::std::min<u16>(::std::max<u16>(0, v), static_cast<u16>((1 << 8) - 1)); +            return static_cast<u8>(v); +        } + +        assert(false && "We shouldn't get here."); +        return 0; +    } + +    const ChannelType& A() const { +        return color[0]; +    } +    ChannelType& A() { +        return color[0]; +    } +    const ChannelType& R() const { +        return color[1]; +    } +    ChannelType& R() { +        return color[1]; +    } +    const ChannelType& G() const { +        return color[2]; +    } +    ChannelType& G() { +        return color[2]; +    } +    const ChannelType& B() const { +        return color[3]; +    } +    ChannelType& B() { +        return color[3]; +    } +    const ChannelType& Component(u32 idx) const { +        return color[idx]; +    } +    ChannelType& Component(u32 idx) { +        return color[idx]; +    } + +    void GetBitDepth(u8 (&outDepth)[4]) const { +        for (s32 i = 0; i < 4; i++) { +            outDepth[i] = m_BitDepth[i]; +        } +    } + +    // Take all of the components, transform them to their 8-bit variants, +    // and then pack each channel into an R8G8B8A8 32-bit integer. We assume +    // that the architecture is little-endian, so the alpha channel will end +    // up in the most-significant byte. +    u32 Pack() const { +        Pixel eightBit(*this); +        eightBit.ChangeBitDepth(); + +        u32 r = 0; +        r |= eightBit.A(); +        r <<= 8; +        r |= eightBit.B(); +        r <<= 8; +        r |= eightBit.G(); +        r <<= 8; +        r |= eightBit.R(); +        return r; +    } + +    // Clamps the pixel to the range [0,255] +    void ClampByte() { +        for (u32 i = 0; i < 4; i++) { +            color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]); +        } +    } + +    void MakeOpaque() { +        A() = 255; +    } +}; + +static void DecodeColorValues(u32* out, std::span<u8> data, const u32* modes, const u32 nPartitions, +                              const u32 nBitsForColorData) { +    // First figure out how many color values we have +    u32 nValues = 0; +    for (u32 i = 0; i < nPartitions; i++) { +        nValues += ((modes[i] >> 2) + 1) << 1; +    } + +    // Then based on the number of values and the remaining number of bits, +    // figure out the max value for each of them... +    u32 range = 256; +    while (--range > 0) { +        IntegerEncodedValue val = ASTC_ENCODINGS_VALUES[range]; +        u32 bitLength = val.GetBitLength(nValues); +        if (bitLength <= nBitsForColorData) { +            // Find the smallest possible range that matches the given encoding +            while (--range > 0) { +                IntegerEncodedValue newval = ASTC_ENCODINGS_VALUES[range]; +                if (!newval.MatchesEncoding(val)) { +                    break; +                } +            } + +            // Return to last matching range. +            range++; +            break; +        } +    } + +    // We now have enough to decode our integer sequence. +    IntegerEncodedVector decodedColorValues; + +    InputBitStream colorStream(data, 0); +    DecodeIntegerSequence(decodedColorValues, colorStream, range, nValues); + +    // Once we have the decoded values, we need to dequantize them to the 0-255 range +    // This procedure is outlined in ASTC spec C.2.13 +    u32 outIdx = 0; +    for (auto itr = decodedColorValues.begin(); itr != decodedColorValues.end(); ++itr) { +        // Have we already decoded all that we need? +        if (outIdx >= nValues) { +            break; +        } + +        const IntegerEncodedValue& val = *itr; +        u32 bitlen = val.num_bits; +        u32 bitval = val.bit_value; + +        assert(bitlen >= 1); + +        u32 A = 0, B = 0, C = 0, D = 0; +        // A is just the lsb replicated 9 times. +        A = ReplicateBitTo9(bitval & 1); + +        switch (val.encoding) { +        // Replicate bits +        case IntegerEncoding::JustBits: +            out[outIdx++] = FastReplicateTo8(bitval, bitlen); +            break; + +        // Use algorithm in C.2.13 +        case IntegerEncoding::Trit: { + +            D = val.trit_value; + +            switch (bitlen) { +            case 1: { +                C = 204; +            } break; + +            case 2: { +                C = 93; +                // B = b000b0bb0 +                u32 b = (bitval >> 1) & 1; +                B = (b << 8) | (b << 4) | (b << 2) | (b << 1); +            } break; + +            case 3: { +                C = 44; +                // B = cb000cbcb +                u32 cb = (bitval >> 1) & 3; +                B = (cb << 7) | (cb << 2) | cb; +            } break; + +            case 4: { +                C = 22; +                // B = dcb000dcb +                u32 dcb = (bitval >> 1) & 7; +                B = (dcb << 6) | dcb; +            } break; + +            case 5: { +                C = 11; +                // B = edcb000ed +                u32 edcb = (bitval >> 1) & 0xF; +                B = (edcb << 5) | (edcb >> 2); +            } break; + +            case 6: { +                C = 5; +                // B = fedcb000f +                u32 fedcb = (bitval >> 1) & 0x1F; +                B = (fedcb << 4) | (fedcb >> 4); +            } break; + +            default: +                assert(false && "Unsupported trit encoding for color values!"); +                break; +            } // switch(bitlen) +        }     // case IntegerEncoding::Trit +        break; + +        case IntegerEncoding::Quint: { + +            D = val.quint_value; + +            switch (bitlen) { +            case 1: { +                C = 113; +            } break; + +            case 2: { +                C = 54; +                // B = b0000bb00 +                u32 b = (bitval >> 1) & 1; +                B = (b << 8) | (b << 3) | (b << 2); +            } break; + +            case 3: { +                C = 26; +                // B = cb0000cbc +                u32 cb = (bitval >> 1) & 3; +                B = (cb << 7) | (cb << 1) | (cb >> 1); +            } break; + +            case 4: { +                C = 13; +                // B = dcb0000dc +                u32 dcb = (bitval >> 1) & 7; +                B = (dcb << 6) | (dcb >> 1); +            } break; + +            case 5: { +                C = 6; +                // B = edcb0000e +                u32 edcb = (bitval >> 1) & 0xF; +                B = (edcb << 5) | (edcb >> 3); +            } break; + +            default: +                assert(false && "Unsupported quint encoding for color values!"); +                break; +            } // switch(bitlen) +        }     // case IntegerEncoding::Quint +        break; +        } // switch(val.encoding) + +        if (val.encoding != IntegerEncoding::JustBits) { +            u32 T = D * C + B; +            T ^= A; +            T = (A & 0x80) | (T >> 2); +            out[outIdx++] = T; +        } +    } + +    // Make sure that each of our values is in the proper range... +    for (u32 i = 0; i < nValues; i++) { +        assert(out[i] <= 255); +    } +} + +static u32 UnquantizeTexelWeight(const IntegerEncodedValue& val) { +    u32 bitval = val.bit_value; +    u32 bitlen = val.num_bits; + +    u32 A = ReplicateBitTo7(bitval & 1); +    u32 B = 0, C = 0, D = 0; + +    u32 result = 0; +    switch (val.encoding) { +    case IntegerEncoding::JustBits: +        result = FastReplicateTo6(bitval, bitlen); +        break; + +    case IntegerEncoding::Trit: { +        D = val.trit_value; +        assert(D < 3); + +        switch (bitlen) { +        case 0: { +            u32 results[3] = {0, 32, 63}; +            result = results[D]; +        } break; + +        case 1: { +            C = 50; +        } break; + +        case 2: { +            C = 23; +            u32 b = (bitval >> 1) & 1; +            B = (b << 6) | (b << 2) | b; +        } break; + +        case 3: { +            C = 11; +            u32 cb = (bitval >> 1) & 3; +            B = (cb << 5) | cb; +        } break; + +        default: +            assert(false && "Invalid trit encoding for texel weight"); +            break; +        } +    } break; + +    case IntegerEncoding::Quint: { +        D = val.quint_value; +        assert(D < 5); + +        switch (bitlen) { +        case 0: { +            u32 results[5] = {0, 16, 32, 47, 63}; +            result = results[D]; +        } break; + +        case 1: { +            C = 28; +        } break; + +        case 2: { +            C = 13; +            u32 b = (bitval >> 1) & 1; +            B = (b << 6) | (b << 1); +        } break; + +        default: +            assert(false && "Invalid quint encoding for texel weight"); +            break; +        } +    } break; +    } + +    if (val.encoding != IntegerEncoding::JustBits && bitlen > 0) { +        // Decode the value... +        result = D * C + B; +        result ^= A; +        result = (A & 0x20) | (result >> 2); +    } + +    assert(result < 64); + +    // Change from [0,63] to [0,64] +    if (result > 32) { +        result += 1; +    } + +    return result; +} + +static void UnquantizeTexelWeights(u32 out[2][144], const IntegerEncodedVector& weights, +                                   const TexelWeightParams& params, const u32 blockWidth, +                                   const u32 blockHeight) { +    u32 weightIdx = 0; +    u32 unquantized[2][144]; + +    for (auto itr = weights.begin(); itr != weights.end(); ++itr) { +        unquantized[0][weightIdx] = UnquantizeTexelWeight(*itr); + +        if (params.m_bDualPlane) { +            ++itr; +            unquantized[1][weightIdx] = UnquantizeTexelWeight(*itr); +            if (itr == weights.end()) { +                break; +            } +        } + +        if (++weightIdx >= (params.m_Width * params.m_Height)) +            break; +    } + +    // Do infill if necessary (Section C.2.18) ... +    u32 Ds = (1024 + (blockWidth / 2)) / (blockWidth - 1); +    u32 Dt = (1024 + (blockHeight / 2)) / (blockHeight - 1); + +    const u32 kPlaneScale = params.m_bDualPlane ? 2U : 1U; +    for (u32 plane = 0; plane < kPlaneScale; plane++) +        for (u32 t = 0; t < blockHeight; t++) +            for (u32 s = 0; s < blockWidth; s++) { +                u32 cs = Ds * s; +                u32 ct = Dt * t; + +                u32 gs = (cs * (params.m_Width - 1) + 32) >> 6; +                u32 gt = (ct * (params.m_Height - 1) + 32) >> 6; + +                u32 js = gs >> 4; +                u32 fs = gs & 0xF; + +                u32 jt = gt >> 4; +                u32 ft = gt & 0x0F; + +                u32 w11 = (fs * ft + 8) >> 4; +                u32 w10 = ft - w11; +                u32 w01 = fs - w11; +                u32 w00 = 16 - fs - ft + w11; + +                u32 v0 = js + jt * params.m_Width; + +#define FIND_TEXEL(tidx, bidx)                                                                     \ +    u32 p##bidx = 0;                                                                               \ +    do {                                                                                           \ +        if ((tidx) < (params.m_Width * params.m_Height)) {                                         \ +            p##bidx = unquantized[plane][(tidx)];                                                  \ +        }                                                                                          \ +    } while (0) + +                FIND_TEXEL(v0, 00); +                FIND_TEXEL(v0 + 1, 01); +                FIND_TEXEL(v0 + params.m_Width, 10); +                FIND_TEXEL(v0 + params.m_Width + 1, 11); + +#undef FIND_TEXEL + +                out[plane][t * blockWidth + s] = +                    (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4; +            } +} + +// Transfers a bit as described in C.2.14 +static inline void BitTransferSigned(int& a, int& b) { +    b >>= 1; +    b |= a & 0x80; +    a >>= 1; +    a &= 0x3F; +    if (a & 0x20) +        a -= 0x40; +} + +// Adds more precision to the blue channel as described +// in C.2.14 +static inline Pixel BlueContract(s32 a, s32 r, s32 g, s32 b) { +    return Pixel(static_cast<s16>(a), static_cast<s16>((r + b) >> 1), +                 static_cast<s16>((g + b) >> 1), static_cast<s16>(b)); +} + +// Partition selection functions as specified in +// C.2.21 +static inline u32 hash52(u32 p) { +    p ^= p >> 15; +    p -= p << 17; +    p += p << 7; +    p += p << 4; +    p ^= p >> 5; +    p += p << 16; +    p ^= p >> 7; +    p ^= p >> 3; +    p ^= p << 6; +    p ^= p >> 17; +    return p; +} + +static u32 SelectPartition(s32 seed, s32 x, s32 y, s32 z, s32 partitionCount, s32 smallBlock) { +    if (1 == partitionCount) +        return 0; + +    if (smallBlock) { +        x <<= 1; +        y <<= 1; +        z <<= 1; +    } + +    seed += (partitionCount - 1) * 1024; + +    u32 rnum = hash52(static_cast<u32>(seed)); +    u8 seed1 = static_cast<u8>(rnum & 0xF); +    u8 seed2 = static_cast<u8>((rnum >> 4) & 0xF); +    u8 seed3 = static_cast<u8>((rnum >> 8) & 0xF); +    u8 seed4 = static_cast<u8>((rnum >> 12) & 0xF); +    u8 seed5 = static_cast<u8>((rnum >> 16) & 0xF); +    u8 seed6 = static_cast<u8>((rnum >> 20) & 0xF); +    u8 seed7 = static_cast<u8>((rnum >> 24) & 0xF); +    u8 seed8 = static_cast<u8>((rnum >> 28) & 0xF); +    u8 seed9 = static_cast<u8>((rnum >> 18) & 0xF); +    u8 seed10 = static_cast<u8>((rnum >> 22) & 0xF); +    u8 seed11 = static_cast<u8>((rnum >> 26) & 0xF); +    u8 seed12 = static_cast<u8>(((rnum >> 30) | (rnum << 2)) & 0xF); + +    seed1 = static_cast<u8>(seed1 * seed1); +    seed2 = static_cast<u8>(seed2 * seed2); +    seed3 = static_cast<u8>(seed3 * seed3); +    seed4 = static_cast<u8>(seed4 * seed4); +    seed5 = static_cast<u8>(seed5 * seed5); +    seed6 = static_cast<u8>(seed6 * seed6); +    seed7 = static_cast<u8>(seed7 * seed7); +    seed8 = static_cast<u8>(seed8 * seed8); +    seed9 = static_cast<u8>(seed9 * seed9); +    seed10 = static_cast<u8>(seed10 * seed10); +    seed11 = static_cast<u8>(seed11 * seed11); +    seed12 = static_cast<u8>(seed12 * seed12); + +    s32 sh1, sh2, sh3; +    if (seed & 1) { +        sh1 = (seed & 2) ? 4 : 5; +        sh2 = (partitionCount == 3) ? 6 : 5; +    } else { +        sh1 = (partitionCount == 3) ? 6 : 5; +        sh2 = (seed & 2) ? 4 : 5; +    } +    sh3 = (seed & 0x10) ? sh1 : sh2; + +    seed1 = static_cast<u8>(seed1 >> sh1); +    seed2 = static_cast<u8>(seed2 >> sh2); +    seed3 = static_cast<u8>(seed3 >> sh1); +    seed4 = static_cast<u8>(seed4 >> sh2); +    seed5 = static_cast<u8>(seed5 >> sh1); +    seed6 = static_cast<u8>(seed6 >> sh2); +    seed7 = static_cast<u8>(seed7 >> sh1); +    seed8 = static_cast<u8>(seed8 >> sh2); +    seed9 = static_cast<u8>(seed9 >> sh3); +    seed10 = static_cast<u8>(seed10 >> sh3); +    seed11 = static_cast<u8>(seed11 >> sh3); +    seed12 = static_cast<u8>(seed12 >> sh3); + +    s32 a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14); +    s32 b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10); +    s32 c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6); +    s32 d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2); + +    a &= 0x3F; +    b &= 0x3F; +    c &= 0x3F; +    d &= 0x3F; + +    if (partitionCount < 4) +        d = 0; +    if (partitionCount < 3) +        c = 0; + +    if (a >= b && a >= c && a >= d) +        return 0; +    else if (b >= c && b >= d) +        return 1; +    else if (c >= d) +        return 2; +    return 3; +} + +static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount, s32 smallBlock) { +    return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); +} + +// Section C.2.14 +static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, +                             u32 colorEndpointMode) { +#define READ_UINT_VALUES(N)                                                                        \ +    u32 v[N];                                                                                      \ +    for (u32 i = 0; i < N; i++) {                                                                  \ +        v[i] = *(colorValues++);                                                                   \ +    } + +#define READ_INT_VALUES(N)                                                                         \ +    s32 v[N];                                                                                      \ +    for (u32 i = 0; i < N; i++) {                                                                  \ +        v[i] = static_cast<int>(*(colorValues++));                                                 \ +    } + +    switch (colorEndpointMode) { +    case 0: { +        READ_UINT_VALUES(2) +        ep1 = Pixel(0xFF, v[0], v[0], v[0]); +        ep2 = Pixel(0xFF, v[1], v[1], v[1]); +    } break; + +    case 1: { +        READ_UINT_VALUES(2) +        u32 L0 = (v[0] >> 2) | (v[1] & 0xC0); +        u32 L1 = std::min(L0 + (v[1] & 0x3F), 0xFFU); +        ep1 = Pixel(0xFF, L0, L0, L0); +        ep2 = Pixel(0xFF, L1, L1, L1); +    } break; + +    case 4: { +        READ_UINT_VALUES(4) +        ep1 = Pixel(v[2], v[0], v[0], v[0]); +        ep2 = Pixel(v[3], v[1], v[1], v[1]); +    } break; + +    case 5: { +        READ_INT_VALUES(4) +        BitTransferSigned(v[1], v[0]); +        BitTransferSigned(v[3], v[2]); +        ep1 = Pixel(v[2], v[0], v[0], v[0]); +        ep2 = Pixel(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1]); +        ep1.ClampByte(); +        ep2.ClampByte(); +    } break; + +    case 6: { +        READ_UINT_VALUES(4) +        ep1 = Pixel(0xFF, v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); +        ep2 = Pixel(0xFF, v[0], v[1], v[2]); +    } break; + +    case 8: { +        READ_UINT_VALUES(6) +        if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { +            ep1 = Pixel(0xFF, v[0], v[2], v[4]); +            ep2 = Pixel(0xFF, v[1], v[3], v[5]); +        } else { +            ep1 = BlueContract(0xFF, v[1], v[3], v[5]); +            ep2 = BlueContract(0xFF, v[0], v[2], v[4]); +        } +    } break; + +    case 9: { +        READ_INT_VALUES(6) +        BitTransferSigned(v[1], v[0]); +        BitTransferSigned(v[3], v[2]); +        BitTransferSigned(v[5], v[4]); +        if (v[1] + v[3] + v[5] >= 0) { +            ep1 = Pixel(0xFF, v[0], v[2], v[4]); +            ep2 = Pixel(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); +        } else { +            ep1 = BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); +            ep2 = BlueContract(0xFF, v[0], v[2], v[4]); +        } +        ep1.ClampByte(); +        ep2.ClampByte(); +    } break; + +    case 10: { +        READ_UINT_VALUES(6) +        ep1 = Pixel(v[4], v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); +        ep2 = Pixel(v[5], v[0], v[1], v[2]); +    } break; + +    case 12: { +        READ_UINT_VALUES(8) +        if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { +            ep1 = Pixel(v[6], v[0], v[2], v[4]); +            ep2 = Pixel(v[7], v[1], v[3], v[5]); +        } else { +            ep1 = BlueContract(v[7], v[1], v[3], v[5]); +            ep2 = BlueContract(v[6], v[0], v[2], v[4]); +        } +    } break; + +    case 13: { +        READ_INT_VALUES(8) +        BitTransferSigned(v[1], v[0]); +        BitTransferSigned(v[3], v[2]); +        BitTransferSigned(v[5], v[4]); +        BitTransferSigned(v[7], v[6]); +        if (v[1] + v[3] + v[5] >= 0) { +            ep1 = Pixel(v[6], v[0], v[2], v[4]); +            ep2 = Pixel(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5]); +        } else { +            ep1 = BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5]); +            ep2 = BlueContract(v[6], v[0], v[2], v[4]); +        } +        ep1.ClampByte(); +        ep2.ClampByte(); +    } break; + +    default: +        assert(false && "Unsupported color endpoint mode (is it HDR?)"); +        break; +    } + +#undef READ_UINT_VALUES +#undef READ_INT_VALUES +} + +static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth, +                            const u32 blockHeight, std::span<u32, 12 * 12> outBuf) { +    InputBitStream strm(inBuf); +    TexelWeightParams weightParams = DecodeBlockInfo(strm); + +    // Was there an error? +    if (weightParams.m_bError) { +        assert(false && "Invalid block mode"); +        FillError(outBuf, blockWidth, blockHeight); +        return; +    } + +    if (weightParams.m_bVoidExtentLDR) { +        FillVoidExtentLDR(strm, outBuf, blockWidth, blockHeight); +        return; +    } + +    if (weightParams.m_bVoidExtentHDR) { +        assert(false && "HDR void extent blocks are unsupported!"); +        FillError(outBuf, blockWidth, blockHeight); +        return; +    } + +    if (weightParams.m_Width > blockWidth) { +        assert(false && "Texel weight grid width should be smaller than block width"); +        FillError(outBuf, blockWidth, blockHeight); +        return; +    } + +    if (weightParams.m_Height > blockHeight) { +        assert(false && "Texel weight grid height should be smaller than block height"); +        FillError(outBuf, blockWidth, blockHeight); +        return; +    } + +    // Read num partitions +    u32 nPartitions = strm.ReadBits<2>() + 1; +    assert(nPartitions <= 4); + +    if (nPartitions == 4 && weightParams.m_bDualPlane) { +        assert(false && "Dual plane mode is incompatible with four partition blocks"); +        FillError(outBuf, blockWidth, blockHeight); +        return; +    } + +    // Based on the number of partitions, read the color endpoint mode for +    // each partition. + +    // Determine partitions, partition index, and color endpoint modes +    s32 planeIdx = -1; +    u32 partitionIndex; +    u32 colorEndpointMode[4] = {0, 0, 0, 0}; + +    // Define color data. +    u8 colorEndpointData[16]; +    memset(colorEndpointData, 0, sizeof(colorEndpointData)); +    OutputBitStream colorEndpointStream(colorEndpointData, 16 * 8, 0); + +    // Read extra config data... +    u32 baseCEM = 0; +    if (nPartitions == 1) { +        colorEndpointMode[0] = strm.ReadBits<4>(); +        partitionIndex = 0; +    } else { +        partitionIndex = strm.ReadBits<10>(); +        baseCEM = strm.ReadBits<6>(); +    } +    u32 baseMode = (baseCEM & 3); + +    // Remaining bits are color endpoint data... +    u32 nWeightBits = weightParams.GetPackedBitSize(); +    s32 remainingBits = 128 - nWeightBits - static_cast<int>(strm.GetBitsRead()); + +    // Consider extra bits prior to texel data... +    u32 extraCEMbits = 0; +    if (baseMode) { +        switch (nPartitions) { +        case 2: +            extraCEMbits += 2; +            break; +        case 3: +            extraCEMbits += 5; +            break; +        case 4: +            extraCEMbits += 8; +            break; +        default: +            assert(false); +            break; +        } +    } +    remainingBits -= extraCEMbits; + +    // Do we have a dual plane situation? +    u32 planeSelectorBits = 0; +    if (weightParams.m_bDualPlane) { +        planeSelectorBits = 2; +    } +    remainingBits -= planeSelectorBits; + +    // Read color data... +    u32 colorDataBits = remainingBits; +    while (remainingBits > 0) { +        u32 nb = std::min(remainingBits, 8); +        u32 b = strm.ReadBits(nb); +        colorEndpointStream.WriteBits(b, nb); +        remainingBits -= 8; +    } + +    // Read the plane selection bits +    planeIdx = strm.ReadBits(planeSelectorBits); + +    // Read the rest of the CEM +    if (baseMode) { +        u32 extraCEM = strm.ReadBits(extraCEMbits); +        u32 CEM = (extraCEM << 6) | baseCEM; +        CEM >>= 2; + +        bool C[4] = {0}; +        for (u32 i = 0; i < nPartitions; i++) { +            C[i] = CEM & 1; +            CEM >>= 1; +        } + +        u8 M[4] = {0}; +        for (u32 i = 0; i < nPartitions; i++) { +            M[i] = CEM & 3; +            CEM >>= 2; +            assert(M[i] <= 3); +        } + +        for (u32 i = 0; i < nPartitions; i++) { +            colorEndpointMode[i] = baseMode; +            if (!(C[i])) +                colorEndpointMode[i] -= 1; +            colorEndpointMode[i] <<= 2; +            colorEndpointMode[i] |= M[i]; +        } +    } else if (nPartitions > 1) { +        u32 CEM = baseCEM >> 2; +        for (u32 i = 0; i < nPartitions; i++) { +            colorEndpointMode[i] = CEM; +        } +    } + +    // Make sure everything up till here is sane. +    for (u32 i = 0; i < nPartitions; i++) { +        assert(colorEndpointMode[i] < 16); +    } +    assert(strm.GetBitsRead() + weightParams.GetPackedBitSize() == 128); + +    // Decode both color data and texel weight data +    u32 colorValues[32]; // Four values, two endpoints, four maximum paritions +    DecodeColorValues(colorValues, colorEndpointData, colorEndpointMode, nPartitions, +                      colorDataBits); + +    Pixel endpoints[4][2]; +    const u32* colorValuesPtr = colorValues; +    for (u32 i = 0; i < nPartitions; i++) { +        ComputeEndpoints(endpoints[i][0], endpoints[i][1], colorValuesPtr, colorEndpointMode[i]); +    } + +    // Read the texel weight data.. +    std::array<u8, 16> texelWeightData; +    std::ranges::copy(inBuf, texelWeightData.begin()); + +    // Reverse everything +    for (u32 i = 0; i < 8; i++) { +// Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits +#define REVERSE_BYTE(b) (((b)*0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32 +        u8 a = static_cast<u8>(REVERSE_BYTE(texelWeightData[i])); +        u8 b = static_cast<u8>(REVERSE_BYTE(texelWeightData[15 - i])); +#undef REVERSE_BYTE + +        texelWeightData[i] = b; +        texelWeightData[15 - i] = a; +    } + +    // Make sure that higher non-texel bits are set to zero +    const u32 clearByteStart = (weightParams.GetPackedBitSize() >> 3) + 1; +    if (clearByteStart > 0 && clearByteStart <= texelWeightData.size()) { +        texelWeightData[clearByteStart - 1] &= +            static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1); +        std::memset(texelWeightData.data() + clearByteStart, 0, +                    std::min(16U - clearByteStart, 16U)); +    } + +    IntegerEncodedVector texelWeightValues; + +    InputBitStream weightStream(texelWeightData); + +    DecodeIntegerSequence(texelWeightValues, weightStream, weightParams.m_MaxWeight, +                          weightParams.GetNumWeightValues()); + +    // Blocks can be at most 12x12, so we can have as many as 144 weights +    u32 weights[2][144]; +    UnquantizeTexelWeights(weights, texelWeightValues, weightParams, blockWidth, blockHeight); + +    // Now that we have endpoints and weights, we can interpolate and generate +    // the proper decoding... +    for (u32 j = 0; j < blockHeight; j++) +        for (u32 i = 0; i < blockWidth; i++) { +            u32 partition = Select2DPartition(partitionIndex, i, j, nPartitions, +                                              (blockHeight * blockWidth) < 32); +            assert(partition < nPartitions); + +            Pixel p; +            for (u32 c = 0; c < 4; c++) { +                u32 C0 = endpoints[partition][0].Component(c); +                C0 = ReplicateByteTo16(C0); +                u32 C1 = endpoints[partition][1].Component(c); +                C1 = ReplicateByteTo16(C1); + +                u32 plane = 0; +                if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { +                    plane = 1; +                } + +                u32 weight = weights[plane][j * blockWidth + i]; +                u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; +                if (C == 65535) { +                    p.Component(c) = 255; +                } else { +                    double Cf = static_cast<double>(C); +                    p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5); +                } +            } + +            outBuf[j * blockWidth + i] = p.Pack(); +        } +} + +void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, +                uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) { +    u32 block_index = 0; +    std::size_t depth_offset = 0; +    for (u32 z = 0; z < depth; z++) { +        for (u32 y = 0; y < height; y += block_height) { +            for (u32 x = 0; x < width; x += block_width) { +                const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)}; + +                // Blocks can be at most 12x12 +                std::array<u32, 12 * 12> uncompData; +                DecompressBlock(blockPtr, block_width, block_height, uncompData); + +                u32 decompWidth = std::min(block_width, width - x); +                u32 decompHeight = std::min(block_height, height - y); + +                const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4); +                for (u32 jj = 0; jj < decompHeight; jj++) { +                    std::memcpy(outRow.data() + jj * width * 4, +                                uncompData.data() + jj * block_width, decompWidth * 4); +                } +                ++block_index; +            } +        } +        depth_offset += height * width * 4; +    } +} + +} // namespace Tegra::Texture::ASTC diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h index c1c73fda5..0229ae122 100644 --- a/src/video_core/textures/astc.h +++ b/src/video_core/textures/astc.h @@ -77,7 +77,7 @@ constexpr std::array<IntegerEncodedValue, 256> MakeEncodedValues() {      return encodings;  } -constexpr std::array<IntegerEncodedValue, 256> EncodingsValues = MakeEncodedValues(); +constexpr std::array<IntegerEncodedValue, 256> ASTC_ENCODINGS_VALUES = MakeEncodedValues();  // Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)]  // is the same as [(num_bits - 1):0] and repeats all the way down. @@ -116,17 +116,11 @@ constexpr auto MakeReplicateTable() {      return table;  } -constexpr auto REPLICATE_BYTE_TO_16_TABLE = MakeReplicateTable<u32, 8, 16>();  constexpr auto REPLICATE_6_BIT_TO_8_TABLE = MakeReplicateTable<u32, 6, 8>();  constexpr auto REPLICATE_7_BIT_TO_8_TABLE = MakeReplicateTable<u32, 7, 8>();  constexpr auto REPLICATE_8_BIT_TO_8_TABLE = MakeReplicateTable<u32, 8, 8>(); -struct AstcBufferData { -    decltype(EncodingsValues) encoding_values = EncodingsValues; -    decltype(REPLICATE_6_BIT_TO_8_TABLE) replicate_6_to_8 = REPLICATE_6_BIT_TO_8_TABLE; -    decltype(REPLICATE_7_BIT_TO_8_TABLE) replicate_7_to_8 = REPLICATE_7_BIT_TO_8_TABLE; -    decltype(REPLICATE_8_BIT_TO_8_TABLE) replicate_8_to_8 = REPLICATE_8_BIT_TO_8_TABLE; -    decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE; -} constexpr ASTC_BUFFER_DATA; +void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, +                uint32_t block_width, uint32_t block_height, std::span<uint8_t> output);  } // namespace Tegra::Texture::ASTC diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp index f0ee76519..758c038ba 100644 --- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp @@ -50,7 +50,7 @@ NsightAftermathTracker::NsightAftermathTracker() {      }      dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash"; -    void(Common::FS::RemoveDirRecursively(dump_dir)); +    Common::FS::RemoveDirRecursively(dump_dir);      if (!Common::FS::CreateDir(dump_dir)) {          LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");          return; diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index 5c64c9bf7..0f60765bb 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp @@ -12,6 +12,14 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,                    VkDebugUtilsMessageTypeFlagsEXT type,                    const VkDebugUtilsMessengerCallbackDataEXT* data,                    [[maybe_unused]] void* user_data) { +    // Skip logging known false-positive validation errors +    switch (static_cast<u32>(data->messageIdNumber)) { +    case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter +    case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0]) +        return VK_FALSE; +    default: +        break; +    }      const std::string_view message{data->pMessage};      if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {          LOG_CRITICAL(Render_Vulkan, "{}", message); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 64206b3d2..f214510da 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -408,6 +408,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR      }      logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld); +    CollectPhysicalMemoryInfo();      CollectTelemetryParameters();      CollectToolingInfo(); @@ -531,6 +532,27 @@ bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags want      return (supported_usage & wanted_usage) == wanted_usage;  } +std::string Device::GetDriverName() const { +    switch (driver_id) { +    case VK_DRIVER_ID_AMD_PROPRIETARY: +        return "AMD"; +    case VK_DRIVER_ID_AMD_OPEN_SOURCE: +        return "AMDVLK"; +    case VK_DRIVER_ID_MESA_RADV: +        return "RADV"; +    case VK_DRIVER_ID_NVIDIA_PROPRIETARY: +        return "NVIDIA"; +    case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS: +        return "INTEL"; +    case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA: +        return "ANV"; +    case VK_DRIVER_ID_MESA_LLVMPIPE: +        return "LAVAPIPE"; +    default: +        return vendor_name; +    } +} +  void Device::CheckSuitability(bool requires_swapchain) const {      std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;      bool has_swapchain = false; @@ -818,6 +840,17 @@ void Device::CollectTelemetryParameters() {      }  } +void Device::CollectPhysicalMemoryInfo() { +    const auto mem_properties = physical.GetMemoryProperties(); +    const size_t num_properties = mem_properties.memoryHeapCount; +    device_access_memory = 0; +    for (size_t element = 0; element < num_properties; ++element) { +        if ((mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { +            device_access_memory += mem_properties.memoryHeaps[element].size; +        } +    } +} +  void Device::CollectToolingInfo() {      if (!ext_tooling_info) {          return; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 67d70cd22..96c0f8c60 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -45,6 +45,9 @@ public:      /// Reports a shader to Nsight Aftermath.      void SaveShader(const std::vector<u32>& spirv) const; +    /// Returns the name of the VkDriverId reported from Vulkan. +    std::string GetDriverName() const; +      /// Returns the dispatch loader with direct function pointers of the device.      const vk::DeviceDispatch& GetDispatchLoader() const {          return dld; @@ -225,6 +228,10 @@ public:          return use_asynchronous_shaders;      } +    u64 GetDeviceLocalMemory() const { +        return device_access_memory; +    } +  private:      /// Checks if the physical device is suitable.      void CheckSuitability(bool requires_swapchain) const; @@ -244,6 +251,9 @@ private:      /// Collects information about attached tools.      void CollectToolingInfo(); +    /// Collects information about the device's local memory. +    void CollectPhysicalMemoryInfo(); +      /// Returns a list of queue initialization descriptors.      std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const; @@ -257,21 +267,22 @@ private:      bool IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,                             FormatType format_type) const; -    VkInstance instance;                    ///< Vulkan instance. -    vk::DeviceDispatch dld;                 ///< Device function pointers. -    vk::PhysicalDevice physical;            ///< Physical device. -    VkPhysicalDeviceProperties properties;  ///< Device properties. -    vk::Device logical;                     ///< Logical device. -    vk::Queue graphics_queue;               ///< Main graphics queue. -    vk::Queue present_queue;                ///< Main present queue. -    u32 instance_version{};                 ///< Vulkan onstance version. -    u32 graphics_family{};                  ///< Main graphics queue family index. -    u32 present_family{};                   ///< Main present queue family index. -    VkDriverIdKHR driver_id{};              ///< Driver ID. -    VkShaderStageFlags guest_warp_stages{}; ///< Stages where the guest warp size can be forced.ed -    bool is_optimal_astc_supported{};       ///< Support for native ASTC. -    bool is_float16_supported{};            ///< Support for float16 arithmetics. -    bool is_warp_potentially_bigger{};      ///< Host warp size can be bigger than guest. +    VkInstance instance;                        ///< Vulkan instance. +    vk::DeviceDispatch dld;                     ///< Device function pointers. +    vk::PhysicalDevice physical;                ///< Physical device. +    VkPhysicalDeviceProperties properties;      ///< Device properties. +    vk::Device logical;                         ///< Logical device. +    vk::Queue graphics_queue;                   ///< Main graphics queue. +    vk::Queue present_queue;                    ///< Main present queue. +    u32 instance_version{};                     ///< Vulkan onstance version. +    u32 graphics_family{};                      ///< Main graphics queue family index. +    u32 present_family{};                       ///< Main present queue family index. +    VkDriverIdKHR driver_id{};                  ///< Driver ID. +    VkShaderStageFlags guest_warp_stages{};     ///< Stages where the guest warp size can be forced. +    u64 device_access_memory{};                 ///< Total size of device local memory in bytes. +    bool is_optimal_astc_supported{};           ///< Support for native ASTC. +    bool is_float16_supported{};                ///< Support for float16 arithmetics. +    bool is_warp_potentially_bigger{};          ///< Host warp size can be bigger than guest.      bool is_formatless_image_load_supported{};  ///< Support for shader image read without format.      bool is_shader_storage_image_multisample{}; ///< Support for image operations on MSAA images.      bool is_blit_depth_stencil_supported{};     ///< Support for blitting from and to depth stencil. diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 5edd06ebc..aa173d19e 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -69,10 +69,10 @@ constexpr VkExportMemoryAllocateInfo EXPORT_ALLOCATE_INFO{  class MemoryAllocation {  public: -    explicit MemoryAllocation(vk::DeviceMemory memory_, VkMemoryPropertyFlags properties, -                              u64 allocation_size_, u32 type) -        : memory{std::move(memory_)}, allocation_size{allocation_size_}, property_flags{properties}, -          shifted_memory_type{1U << type} {} +    explicit MemoryAllocation(MemoryAllocator* const allocator_, vk::DeviceMemory memory_, +                              VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type) +        : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_}, +          property_flags{properties}, shifted_memory_type{1U << type} {}  #if defined(_WIN32) || defined(__unix__)      ~MemoryAllocation() { @@ -106,6 +106,10 @@ public:          const auto it = std::ranges::find(commits, begin, &Range::begin);          ASSERT_MSG(it != commits.end(), "Invalid commit");          commits.erase(it); +        if (commits.empty()) { +            // Do not call any code involving 'this' after this call, the object will be destroyed +            allocator->ReleaseMemory(this); +        }      }      [[nodiscard]] std::span<u8> Map() { @@ -171,6 +175,7 @@ private:          return candidate;      } +    MemoryAllocator* const allocator;           ///< Parent memory allocation.      const vk::DeviceMemory memory;              ///< Vulkan memory allocation handler.      const u64 allocation_size;                  ///< Size of this allocation.      const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags. @@ -275,10 +280,17 @@ bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask,              return false;          }      } -    allocations.push_back(std::make_unique<MemoryAllocation>(std::move(memory), flags, size, type)); +    allocations.push_back( +        std::make_unique<MemoryAllocation>(this, std::move(memory), flags, size, type));      return true;  } +void MemoryAllocator::ReleaseMemory(MemoryAllocation* alloc) { +    const auto it = std::ranges::find(allocations, alloc, &std::unique_ptr<MemoryAllocation>::get); +    ASSERT(it != allocations.end()); +    allocations.erase(it); +} +  std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements,                                                         VkMemoryPropertyFlags flags) {      for (auto& allocation : allocations) { diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index db12d02f4..b61e931e0 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -69,6 +69,8 @@ private:  /// Memory allocator container.  /// Allocates and releases memory allocations on demand.  class MemoryAllocator { +    friend MemoryAllocation; +  public:      /**       * Construct memory allocator @@ -104,6 +106,9 @@ private:      /// Tries to allocate a chunk of memory.      bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size); +    /// Releases a chunk of memory. +    void ReleaseMemory(MemoryAllocation* alloc); +      /// Tries to allocate a memory commit.      std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements,                                            VkMemoryPropertyFlags flags); diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 67183e64c..e04f7dfc6 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -100,8 +100,9 @@ struct Client::Impl {          request.body = data;          httplib::Response response; +        httplib::Error error; -        if (!cli->send(request, response)) { +        if (!cli->send(request, response, error)) {              LOG_ERROR(WebService, "{} to {} returned null", method, host + path);              return WebResult{WebResult::Code::LibError, "Null response", ""};          } diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index a2e0e6962..6b0155a78 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -14,7 +14,8 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia      const auto build_id = std::string(Common::g_build_id);      const auto yuzu_build = fmt::format("yuzu Development Build | {}-{}", branch_name, description); -    const auto override_build = fmt::format(std::string(Common::g_title_bar_format_idle), build_id); +    const auto override_build = +        fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);      const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;      ui->setupUi(this); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 86495803e..7524e3c40 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -51,11 +51,11 @@ void EmuThread::run() {      Common::SetCurrentThreadName(name.c_str());      auto& system = Core::System::GetInstance(); +    auto& gpu = system.GPU(); +    auto stop_token = stop_source.get_token();      system.RegisterHostThread(); -    auto& gpu = system.GPU(); -      // Main process has been loaded. Make the context current to this thread and begin GPU and CPU      // execution.      gpu.Start(); @@ -65,7 +65,7 @@ void EmuThread::run() {      emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);      system.Renderer().ReadRasterizer()->LoadDiskResources( -        system.CurrentProcess()->GetTitleID(), stop_run, +        system.CurrentProcess()->GetTitleID(), stop_token,          [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {              emit LoadProgress(stage, value, total);          }); @@ -78,7 +78,7 @@ void EmuThread::run() {      // so that the DebugModeLeft signal can be emitted before the      // next execution step      bool was_active = false; -    while (!stop_run) { +    while (!stop_token.stop_requested()) {          if (running) {              if (was_active) {                  emit DebugModeLeft(); @@ -100,7 +100,7 @@ void EmuThread::run() {              }              running_guard = false; -            if (!stop_run) { +            if (!stop_token.stop_requested()) {                  was_active = true;                  emit DebugModeEntered();              } @@ -108,7 +108,7 @@ void EmuThread::run() {              UNIMPLEMENTED();          } else {              std::unique_lock lock{running_mutex}; -            running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); +            running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });          }      } diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index acfe2bc8c..402dd2ee1 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -89,16 +89,16 @@ public:       * Requests for the emulation thread to stop running       */      void RequestStop() { -        stop_run = true; +        stop_source.request_stop();          SetRunning(false);      }  private:      bool exec_step = false;      bool running = false; -    std::atomic_bool stop_run{false}; +    std::stop_source stop_source;      std::mutex running_mutex; -    std::condition_variable running_cv; +    std::condition_variable_any running_cv;      Common::Event running_wait{};      std::atomic_bool running_guard{false}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index a59b36e13..62bafc453 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default  // This must be in alphabetical order according to action name as it must have the same order as  // UISetting::values.shortcuts, which is alphabetically ordered.  // clang-format off -const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ +const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{      {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},      {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, @@ -236,6 +236,7 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{      {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},      {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},      {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, +    {QStringLiteral("Toggle Framerate Limit"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},      {QStringLiteral("Toggle Mouse Panning"),     QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},      {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},      {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, @@ -647,6 +648,8 @@ void Config::ReadDebuggingValues() {          ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();      Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();      Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); +    Settings::values.enable_fs_access_log = +        ReadSetting(QStringLiteral("enable_fs_access_log"), false).toBool();      Settings::values.reporting_services =          ReadSetting(QStringLiteral("reporting_services"), false).toBool();      Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); @@ -754,6 +757,8 @@ void Config::ReadCpuValues() {                        QStringLiteral("cpuopt_unsafe_unfuse_fma"), true);      ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error,                        QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); +    ReadSettingGlobal(Settings::values.cpuopt_unsafe_ignore_standard_fpcr, +                      QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), true);      ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan,                        QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true);      ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check, @@ -807,13 +812,17 @@ void Config::ReadRendererValues() {                        QStringLiteral("use_asynchronous_gpu_emulation"), true);      ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"),                        true); +    ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true);      ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); +    ReadSettingGlobal(Settings::values.disable_fps_limit, QStringLiteral("disable_fps_limit"), +                      false);      ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),                        false);      ReadSettingGlobal(Settings::values.use_asynchronous_shaders,                        QStringLiteral("use_asynchronous_shaders"), false);      ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"),                        true); +    ReadSettingGlobal(Settings::values.use_caches_gc, QStringLiteral("use_caches_gc"), false);      ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0);      ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0);      ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); @@ -1258,6 +1267,8 @@ void Config::SaveDebuggingValues() {                   QString::fromStdString(Settings::values.program_args), QString{});      WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);      WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); +    WriteSetting(QStringLiteral("enable_fs_access_log"), Settings::values.enable_fs_access_log, +                 false);      WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false);      WriteSetting(QStringLiteral("use_debug_asserts"), Settings::values.use_debug_asserts, false);      WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false); @@ -1334,6 +1345,8 @@ void Config::SaveCpuValues() {                         Settings::values.cpuopt_unsafe_unfuse_fma, true);      WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),                         Settings::values.cpuopt_unsafe_reduce_fp_error, true); +    WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), +                       Settings::values.cpuopt_unsafe_ignore_standard_fpcr, true);      WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"),                         Settings::values.cpuopt_unsafe_inaccurate_nan, true);      WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"), @@ -1388,13 +1401,17 @@ void Config::SaveRendererValues() {                         Settings::values.use_asynchronous_gpu_emulation, true);      WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation,                         true); +    WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true);      WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); +    WriteSettingGlobal(QStringLiteral("disable_fps_limit"), Settings::values.disable_fps_limit, +                       false);      WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),                         Settings::values.use_assembly_shaders, false);      WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"),                         Settings::values.use_asynchronous_shaders, false);      WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time,                         true); +    WriteSettingGlobal(QStringLiteral("use_caches_gc"), Settings::values.use_caches_gc, false);      // Cast to double because Qt's written float values are not human-readable      WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0);      WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ce3355588..3c1de0ac9 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,7 +42,7 @@ public:          default_mouse_buttons;      static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;      static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; -    static const std::array<UISettings::Shortcut, 17> default_hotkeys; +    static const std::array<UISettings::Shortcut, 18> default_hotkeys;  private:      void Initialize(const std::string& config_name); diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 22219cbad..13db2ba98 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -34,12 +34,15 @@ void ConfigureCpu::SetConfiguration() {      ui->accuracy->setEnabled(runtime_lock);      ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);      ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); +    ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock);      ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);      ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);      ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());      ui->cpuopt_unsafe_reduce_fp_error->setChecked(          Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); +    ui->cpuopt_unsafe_ignore_standard_fpcr->setChecked( +        Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue());      ui->cpuopt_unsafe_inaccurate_nan->setChecked(          Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());      ui->cpuopt_unsafe_fastmem_check->setChecked( @@ -84,6 +87,9 @@ void ConfigureCpu::ApplyConfiguration() {      ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error,                                               ui->cpuopt_unsafe_reduce_fp_error,                                               cpuopt_unsafe_reduce_fp_error); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_standard_fpcr, +                                             ui->cpuopt_unsafe_ignore_standard_fpcr, +                                             cpuopt_unsafe_ignore_standard_fpcr);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan,                                               ui->cpuopt_unsafe_inaccurate_nan,                                               cpuopt_unsafe_inaccurate_nan); @@ -137,6 +143,9 @@ void ConfigureCpu::SetupPerGameUI() {      ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error,                                              Settings::values.cpuopt_unsafe_reduce_fp_error,                                              cpuopt_unsafe_reduce_fp_error); +    ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_standard_fpcr, +                                            Settings::values.cpuopt_unsafe_ignore_standard_fpcr, +                                            cpuopt_unsafe_ignore_standard_fpcr);      ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan,                                              Settings::values.cpuopt_unsafe_inaccurate_nan,                                              cpuopt_unsafe_inaccurate_nan); diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index 57ff2772a..b2b5f1671 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h @@ -40,6 +40,7 @@ private:      ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma;      ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; +    ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr;      ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;      ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;  }; diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index 31ef9e3f5..0e296d4e5 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -112,6 +112,18 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="cpuopt_unsafe_ignore_standard_fpcr"> +          <property name="toolTip"> +           <string> +            <div>This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.</div> +           </string> +          </property> +          <property name="text"> +           <string>Faster ASIMD instructions (32 bits only)</string> +          </property> +         </widget> +        </item> +        <item>           <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan">            <property name="toolTip">             <string> diff --git a/src/yuzu/configuration/configure_cpu_debug.ui b/src/yuzu/configuration/configure_cpu_debug.ui index 11ee19a12..c43f89a5a 100644 --- a/src/yuzu/configuration/configure_cpu_debug.ui +++ b/src/yuzu/configuration/configure_cpu_debug.ui @@ -34,7 +34,7 @@              <br>              If you're not sure what these do, keep all of these enabled.              <br> -            These settings only take effect when CPU Accuracy is "Debug Mode". +            These settings, when disabled, only take effect when CPU Accuracy is "Debug Mode".              </div>             </string>            </property> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index b207e07cb..15d6a5ad7 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -28,17 +28,21 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co  ConfigureDebug::~ConfigureDebug() = default;  void ConfigureDebug::SetConfiguration() { -    ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + +    ui->toggle_console->setEnabled(runtime_lock);      ui->toggle_console->setChecked(UISettings::values.show_console);      ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));      ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); +    ui->fs_access_log->setEnabled(runtime_lock); +    ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log);      ui->reporting_services->setChecked(Settings::values.reporting_services);      ui->quest_flag->setChecked(Settings::values.quest_flag);      ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts);      ui->use_auto_stub->setChecked(Settings::values.use_auto_stub); -    ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    ui->enable_graphics_debugging->setEnabled(runtime_lock);      ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); -    ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    ui->disable_macro_jit->setEnabled(runtime_lock);      ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit);      ui->extended_logging->setChecked(Settings::values.extended_logging);  } @@ -47,6 +51,7 @@ void ConfigureDebug::ApplyConfiguration() {      UISettings::values.show_console = ui->toggle_console->isChecked();      Settings::values.log_filter = ui->log_filter_edit->text().toStdString();      Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); +    Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();      Settings::values.reporting_services = ui->reporting_services->isChecked();      Settings::values.quest_flag = ui->quest_flag->isChecked();      Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index c9e60ee08..c8087542f 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -144,10 +144,17 @@     <item>      <widget class="QGroupBox" name="groupBox_5">       <property name="title"> -      <string>Dump</string> +      <string>Debugging</string>       </property>       <layout class="QVBoxLayout" name="verticalLayout_7">        <item> +       <widget class="QCheckBox" name="fs_access_log"> +        <property name="text"> +         <string>Enable FS Access Log</string> +        </property> +       </widget> +      </item> +      <item>         <widget class="QCheckBox" name="reporting_services">          <property name="text">           <string>Enable Verbose Reporting Services</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index fb9ec093c..41a69d9b8 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -70,10 +70,12 @@ void ConfigureGraphics::SetConfiguration() {      ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);      ui->use_disk_shader_cache->setEnabled(runtime_lock);      ui->use_nvdec_emulation->setEnabled(runtime_lock); +    ui->accelerate_astc->setEnabled(runtime_lock);      ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());      ui->use_asynchronous_gpu_emulation->setChecked(          Settings::values.use_asynchronous_gpu_emulation.GetValue());      ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); +    ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue());      if (Settings::IsConfiguringGlobal()) {          ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); @@ -118,6 +120,8 @@ void ConfigureGraphics::ApplyConfiguration() {                                               use_asynchronous_gpu_emulation);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,                                               ui->use_nvdec_emulation, use_nvdec_emulation); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc, +                                             accelerate_astc);      if (Settings::IsConfiguringGlobal()) {          // Guard if during game and set to game-specific value @@ -254,6 +258,7 @@ void ConfigureGraphics::SetupPerGameUI() {          ui->use_asynchronous_gpu_emulation->setEnabled(              Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());          ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); +        ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());          ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());          ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); @@ -269,6 +274,8 @@ void ConfigureGraphics::SetupPerGameUI() {          ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);      ConfigurationShared::SetColoredTristate(          ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); +    ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc, +                                            accelerate_astc);      ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,                                              Settings::values.use_asynchronous_gpu_emulation,                                              use_asynchronous_gpu_emulation); diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index c162048a2..6418115cf 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -47,6 +47,7 @@ private:      QColor bg_color;      ConfigurationShared::CheckState use_nvdec_emulation; +    ConfigurationShared::CheckState accelerate_astc;      ConfigurationShared::CheckState use_disk_shader_cache;      ConfigurationShared::CheckState use_asynchronous_gpu_emulation; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index ab0bd4d77..5b999d84d 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -105,6 +105,13 @@           </widget>          </item>          <item> +          <widget class="QCheckBox" name="accelerate_astc"> +            <property name="text"> +              <string>Accelerate ASTC texture decoding</string> +            </property> +          </widget> +        </item> +        <item>           <widget class="QWidget" name="fullscreen_mode_layout" native="true">            <layout class="QHBoxLayout" name="horizontalLayout_1">             <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 35bf9c6be..8d13c9857 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,8 +28,10 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {      ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);      ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); +    ui->disable_fps_limit->setChecked(Settings::values.disable_fps_limit.GetValue());      ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());      ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); +    ui->use_caches_gc->setChecked(Settings::values.use_caches_gc.GetValue());      ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());      if (Settings::IsConfiguringGlobal()) { @@ -57,11 +59,15 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {      ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,                                               ui->anisotropic_filtering_combobox);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.disable_fps_limit, +                                             ui->disable_fps_limit, disable_fps_limit);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders,                                               ui->use_assembly_shaders, use_assembly_shaders);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,                                               ui->use_asynchronous_shaders,                                               use_asynchronous_shaders); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_caches_gc, ui->use_caches_gc, +                                             use_caches_gc);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,                                               ui->use_fast_gpu_time, use_fast_gpu_time); @@ -97,10 +103,12 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {      if (Settings::IsConfiguringGlobal()) {          ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());          ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); +        ui->disable_fps_limit->setEnabled(Settings::values.disable_fps_limit.UsingGlobal());          ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());          ui->use_asynchronous_shaders->setEnabled(              Settings::values.use_asynchronous_shaders.UsingGlobal());          ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); +        ui->use_caches_gc->setEnabled(Settings::values.use_caches_gc.UsingGlobal());          ui->anisotropic_filtering_combobox->setEnabled(              Settings::values.max_anisotropy.UsingGlobal()); @@ -108,6 +116,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {      }      ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); +    ConfigurationShared::SetColoredTristate(ui->disable_fps_limit, +                                            Settings::values.disable_fps_limit, disable_fps_limit);      ConfigurationShared::SetColoredTristate(          ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);      ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, @@ -115,6 +125,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {                                              use_asynchronous_shaders);      ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,                                              Settings::values.use_fast_gpu_time, use_fast_gpu_time); +    ConfigurationShared::SetColoredTristate(ui->use_caches_gc, Settings::values.use_caches_gc, +                                            use_caches_gc);      ConfigurationShared::SetColoredComboBox(          ui->gpu_accuracy, ui->label_gpu_accuracy,          static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index e61b571c7..6ac5f20ec 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -35,7 +35,9 @@ private:      std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;      ConfigurationShared::CheckState use_vsync; +    ConfigurationShared::CheckState disable_fps_limit;      ConfigurationShared::CheckState use_assembly_shaders;      ConfigurationShared::CheckState use_asynchronous_shaders;      ConfigurationShared::CheckState use_fast_gpu_time; +    ConfigurationShared::CheckState use_caches_gc;  }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 846a30586..18c43629e 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -77,6 +77,24 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="disable_fps_limit"> +          <property name="enabled"> +           <bool>true</bool> +          </property> +          <property name="toolTip"> +           <string> +            <html><head/><body> +            <p>Presents guest frames as they become available, disabling the FPS limit in most titles.</p> +            <p>NOTE: Will cause instabilities.</p> +            </body></html> +           </string> +          </property> +          <property name="text"> +           <string>Disable framerate limit (experimental)</string> +          </property> +         </widget> +        </item> +        <item>           <widget class="QCheckBox" name="use_assembly_shaders">            <property name="toolTip">             <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string> @@ -104,6 +122,16 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="use_caches_gc"> +          <property name="toolTip"> +           <string>Enables garbage collection for the GPU caches, this will try to keep VRAM within 3-4 GB by flushing the least used textures/buffers. May cause issues in a few games.</string> +          </property> +          <property name="text"> +           <string>Enable GPU cache garbage collection (experimental)</string> +          </property> +         </widget> +        </item> +        <item>           <widget class="QWidget" name="af_layout" native="true">            <layout class="QHBoxLayout" name="horizontalLayout_1">             <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index a1d434aca..8c00eec59 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -47,6 +47,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id, const std::str      ui->setupUi(this);      setFocusPolicy(Qt::ClickFocus);      setWindowTitle(tr("Properties")); +    // remove Help question mark button from the title bar +    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);      ui->addonsTab->SetTitleId(title_id); diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index adf6d0b39..7da14146b 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/src/yuzu/configuration/configure_per_game.ui @@ -6,10 +6,15 @@     <rect>      <x>0</x>      <y>0</y> -    <width>800</width> +    <width>900</width>      <height>600</height>     </rect>    </property> +  <property name="minimumSize"> +   <size> +    <width>900</width> +   </size> +  </property>    <property name="windowTitle">     <string>Dialog</string>    </property> diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 9b709d405..ebb0f411c 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {      std::sort(disabled_addons.begin(), disabled_addons.end());      std::sort(current.begin(), current.end());      if (disabled_addons != current) { -        void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / -                                    "game_list" / fmt::format("{:016X}.pv.txt", title_id))); +        Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / +                               "game_list" / fmt::format("{:016X}.pv.txt", title_id));      }      Settings::values.disabled_addons[title_id] = disabled_addons; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index da956c99b..e44907be8 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -521,7 +521,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri      QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));      remove_menu->addSeparator();      QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); -    QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); +    QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); +    QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); +    QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));      QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));      QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));      context_menu.addSeparator(); @@ -570,8 +572,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri      connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {          emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);      }); -    connect(dump_romfs, &QAction::triggered, -            [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); +    connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { +        emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); +    }); +    connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { +        emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); +    });      connect(copy_tid, &QAction::triggered,              [this, program_id]() { emit CopyTIDRequested(program_id); });      connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index b630e34ff..50402da51 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -45,6 +45,11 @@ enum class GameListRemoveTarget {      CustomConfiguration,  }; +enum class DumpRomFSTarget { +    Normal, +    SDMC, +}; +  enum class InstalledEntryType {      Game,      Update, @@ -92,7 +97,7 @@ signals:      void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);      void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,                               const std::string& game_path); -    void DumpRomFSRequested(u64 program_id, const std::string& game_path); +    void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);      void CopyTIDRequested(u64 program_id);      void NavigateToGamedbEntryRequested(u64 program_id,                                          const CompatibilityList& compatibility_list); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index be8933c5c..f462cd072 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -104,6 +104,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "input_common/main.h"  #include "util/overlay_dialog.h"  #include "video_core/gpu.h" +#include "video_core/renderer_base.h"  #include "video_core/shader_notify.h"  #include "yuzu/about_dialog.h"  #include "yuzu/bootmanager.h" @@ -194,10 +195,10 @@ static void RemoveCachedContents() {      const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";      const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; -    void(Common::FS::RemoveDirRecursively(offline_fonts)); -    void(Common::FS::RemoveDirRecursively(offline_manual)); -    void(Common::FS::RemoveDirRecursively(offline_legal_information)); -    void(Common::FS::RemoveDirRecursively(offline_system_data)); +    Common::FS::RemoveDirRecursively(offline_fonts); +    Common::FS::RemoveDirRecursively(offline_manual); +    Common::FS::RemoveDirRecursively(offline_legal_information); +    Common::FS::RemoveDirRecursively(offline_system_data);  }  GMainWindow::GMainWindow() @@ -236,7 +237,8 @@ GMainWindow::GMainWindow()      const auto build_id = std::string(Common::g_build_id);      const auto yuzu_build = fmt::format("yuzu Development Build | {}-{}", branch_name, description); -    const auto override_build = fmt::format(std::string(Common::g_title_bar_format_idle), build_id); +    const auto override_build = +        fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);      const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;      LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version); @@ -1025,7 +1027,11 @@ void GMainWindow::InitializeHotkeys() {      connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),              &QShortcut::activated, this,              [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); - +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Framerate Limit"), this), +            &QShortcut::activated, this, [] { +                Settings::values.disable_fps_limit.SetValue( +                    !Settings::values.disable_fps_limit.GetValue()); +            });      connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this),              &QShortcut::activated, this, [&] {                  Settings::values.mouse_panning = !Settings::values.mouse_panning; @@ -1417,8 +1423,12 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S          title_name = Common::FS::PathToUTF8String(              std::filesystem::path{filename.toStdU16String()}.filename());      } +    const bool is_64bit = system.Kernel().CurrentProcess()->Is64BitProcess(); +    const auto instruction_set_suffix = is_64bit ? " (64-bit)" : " (32-bit)"; +    title_name += instruction_set_suffix;      LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); -    UpdateWindowTitle(title_name, title_version); +    const auto gpu_vendor = system.GPU().Renderer().GetDeviceVendor(); +    UpdateWindowTitle(title_name, title_version, gpu_vendor);      loading_screen->Prepare(system.GetAppLoader());      loading_screen->show(); @@ -1739,8 +1749,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT          RemoveAddOnContent(program_id, entry_type);          break;      } -    void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / -                                          "game_list")); +    Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / +                                     "game_list");      game_list->PopulateAsync(UISettings::values.game_dirs);  } @@ -1872,7 +1882,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g      }  } -void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { +void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, +                                      DumpRomFSTarget target) {      const auto failed = [this] {          QMessageBox::warning(this, tr("RomFS Extraction Failed!"),                               tr("There was an error copying the RomFS files or the user " @@ -1900,7 +1911,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa          return;      } -    const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir); +    const auto dump_dir = +        target == DumpRomFSTarget::Normal +            ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) +            : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";      const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);      const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); @@ -1910,7 +1924,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa      if (*romfs_title_id == program_id) {          const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();          const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; -        romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program); +        romfs = +            pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);      } else {          romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();      } @@ -2209,8 +2224,8 @@ void GMainWindow::OnMenuInstallToNAND() {                                  : tr("%n file(s) failed to install\n", "", failed_files.size()));      QMessageBox::information(this, tr("Install Results"), install_results); -    void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / -                                          "game_list")); +    Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / +                                     "game_list");      game_list->PopulateAsync(UISettings::values.game_dirs);      ui.action_Install_File_NAND->setEnabled(true);  } @@ -2842,25 +2857,27 @@ void GMainWindow::MigrateConfigFiles() {          LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);          if (!Common::FS::RenameFile(origin, destination)) {              // Delete the old config file if one already exists in the new location. -            void(Common::FS::RemoveFile(origin)); +            Common::FS::RemoveFile(origin);          }      }  } -void GMainWindow::UpdateWindowTitle(const std::string& title_name, -                                    const std::string& title_version) { +void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version, +                                    std::string_view gpu_vendor) {      const auto branch_name = std::string(Common::g_scm_branch);      const auto description = std::string(Common::g_scm_desc);      const auto build_id = std::string(Common::g_build_id);      const auto yuzu_title = fmt::format("yuzu | {}-{}", branch_name, description); -    const auto override_title = fmt::format(std::string(Common::g_title_bar_format_idle), build_id); +    const auto override_title = +        fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);      const auto window_title = override_title.empty() ? yuzu_title : override_title;      if (title_name.empty()) {          setWindowTitle(QString::fromStdString(window_title));      } else { -        const auto run_title = fmt::format("{} | {} | {}", window_title, title_name, title_version); +        const auto run_title = +            fmt::format("{} | {} | {} | {}", window_title, title_name, title_version, gpu_vendor);          setWindowTitle(QString::fromStdString(run_title));      }  } @@ -3036,9 +3053,9 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {          const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); -        void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated")); -        void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated")); -        void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated")); +        Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated"); +        Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated"); +        Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated");      }      Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 11f152cbe..45c8310e1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -34,6 +34,7 @@ class QProgressDialog;  class WaitTreeWidget;  enum class GameListOpenTarget;  enum class GameListRemoveTarget; +enum class DumpRomFSTarget;  enum class InstalledEntryType;  class GameListPlaceholder; @@ -244,7 +245,7 @@ private slots:      void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);      void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,                                const std::string& game_path); -    void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); +    void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);      void OnGameListCopyTID(u64 program_id);      void OnGameListNavigateToGamedbEntry(u64 program_id,                                           const CompatibilityList& compatibility_list); @@ -287,8 +288,8 @@ private:      InstallResult InstallNSPXCI(const QString& filename);      InstallResult InstallNCA(const QString& filename);      void MigrateConfigFiles(); -    void UpdateWindowTitle(const std::string& title_name = {}, -                           const std::string& title_version = {}); +    void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, +                           std::string_view gpu_vendor = {});      void UpdateStatusBar();      void UpdateStatusButtons();      void UpdateUISettings(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 63f368fe5..60bf66ec0 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -443,12 +443,16 @@ void Config::ReadValues() {          sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true));      Settings::values.use_vsync.SetValue(          static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); +    Settings::values.disable_fps_limit.SetValue( +        sdl2_config->GetBoolean("Renderer", "disable_fps_limit", false));      Settings::values.use_assembly_shaders.SetValue(          sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true));      Settings::values.use_asynchronous_shaders.SetValue(          sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); -    Settings::values.use_asynchronous_shaders.SetValue( -        sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); +    Settings::values.use_nvdec_emulation.SetValue( +        sdl2_config->GetBoolean("Renderer", "use_nvdec_emulation", true)); +    Settings::values.accelerate_astc.SetValue( +        sdl2_config->GetBoolean("Renderer", "accelerate_astc", true));      Settings::values.use_fast_gpu_time.SetValue(          sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); @@ -477,6 +481,8 @@ void Config::ReadValues() {      Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");      Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);      Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); +    Settings::values.enable_fs_access_log = +        sdl2_config->GetBoolean("Debugging", "enable_fs_access_log", false);      Settings::values.reporting_services =          sdl2_config->GetBoolean("Debugging", "reporting_services", false);      Settings::values.quest_flag = sdl2_config->GetBoolean("Debugging", "quest_flag", false); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index f48d935a1..cc9850aad 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -194,6 +194,14 @@ use_assembly_shaders =  # 0 (default): Off, 1: On  use_asynchronous_shaders = +# Enable NVDEC emulation. +# 0: Off, 1 (default): On +use_nvdec_emulation = + +# Accelerate ASTC texture decoding. +# 0: Off, 1 (default): On +accelerate_astc = +  # Turns on the frame limiter, which will limit frames output to the target game speed  # 0: Off, 1: On (default)  use_frame_limit = @@ -219,6 +227,10 @@ use_asynchronous_gpu_emulation =  # 0: Off, 1 (default): On  use_vsync = +# Whether to use garbage collection or not for GPU caches. +# 0 (default): Off, 1: On +use_caches_gc = +  # The clear color for the renderer. What shows up on the sides of the bottom screen.  # Must be in range of 0.0-1.0. Defaults to 1.0 for all.  bg_red = @@ -252,7 +264,10 @@ swap_screen =  [Audio]  # Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) +# auto (default): Auto-select +# cubeb: Cubeb audio engine (if available) +# sdl2: SDL2 audio engine (if available) +# null: No audio output  output_engine =  # Whether or not to enable the audio-stretching post-processing effect. @@ -338,6 +353,8 @@ record_frame_times =  dump_exefs=false  # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them  dump_nso=false +# Determines whether or not yuzu will save the filesystem access log. +enable_fs_access_log=false  # Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode  # false: Retail/Normal Mode (default), true: Kiosk Mode  quest_flag = @@ -349,6 +366,9 @@ use_debug_asserts =  use_auto_stub =  # Enables/Disables the macro JIT compiler  disable_macro_jit=false +# Presents guest frames as they become available. Experimental. +# false: Disabled (default), true: Enabled +disable_fps_limit=false  [WebService]  # Whether or not to enable telemetry diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 584967f5c..50e388312 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -219,7 +219,7 @@ int main(int argc, char** argv) {      system.GPU().Start();      system.Renderer().ReadRasterizer()->LoadDiskResources( -        system.CurrentProcess()->GetTitleID(), false, +        system.CurrentProcess()->GetTitleID(), std::stop_token{},          [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});      void(system.Run());  | 
