diff options
54 files changed, 2035 insertions, 6818 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/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 c6125082ea992c245edab26b6f86e7b904b86ee 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/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/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_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/common/fs/file.cpp b/src/common/fs/file.cpp index c84f31f3e..710e88b39 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -183,10 +183,6 @@ 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)) { return 0; } diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 50e270c5b..0f10b6003 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -71,7 +71,7 @@ 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 a file already exists at path, its contents will be erased. * If the filesystem object at path is not a file, this function returns 0. * * @param path Filesystem path @@ -95,7 +95,6 @@ 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. * * @param path Filesystem path 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/settings.cpp b/src/common/settings.cpp index 360e878d6..6397308ec 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -55,6 +55,7 @@ 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()); @@ -121,6 +122,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,6 +137,7 @@ 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); diff --git a/src/common/settings.h b/src/common/settings.h index 1af8c5ac2..85554eac4 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,7 +148,9 @@ 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; @@ -218,6 +221,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/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c8f6dc765..f871f7bf4 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -186,6 +186,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/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/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/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/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/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/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 47190c464..f9454bbaa 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 diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 703e34587..eaba1b103 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -763,7 +763,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; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index ffe9edc1b..9b4038615 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) { 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_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index bdd0ce8bc..52860b4cf 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" @@ -828,7 +829,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) { diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 906604a39..6835fd747 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 { @@ -884,8 +885,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 +1096,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..9b2177ebd --- /dev/null +++ b/src/video_core/textures/astc.cpp @@ -0,0 +1,1577 @@ +// 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 = EncodingsValues[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 EncodingsValues[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 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 = EncodingsValues[range]; + u32 bitLength = val.GetBitLength(nValues); + if (bitLength <= nBitsForColorData) { + // Find the smallest possible range that matches the given encoding + while (--range > 0) { + IntegerEncodedValue newval = EncodingsValues[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..c1c37dfe7 100644 --- a/src/video_core/textures/astc.h +++ b/src/video_core/textures/astc.h @@ -129,4 +129,7 @@ struct AstcBufferData { 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/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/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/configuration/config.cpp b/src/yuzu/configuration/config.cpp index a59b36e13..a1e726dc7 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,7 +812,10 @@ 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, @@ -1258,6 +1266,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 +1344,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,7 +1400,10 @@ 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"), 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_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..1af87e849 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,6 +28,7 @@ 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_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); @@ -57,6 +58,8 @@ 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, @@ -97,6 +100,7 @@ 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()); @@ -108,6 +112,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, diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index e61b571c7..c19c34851 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -35,6 +35,7 @@ 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; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 846a30586..824cb2fb2 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> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index be8933c5c..d4c7d2c0b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1025,7 +1025,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; 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..c960ccf89 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 = @@ -252,7 +260,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 +349,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 +362,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 |