From 97890531eda0c5b55018c5258c3b20ea337cece3 Mon Sep 17 00:00:00 2001
From: stickz <stickman002@mail.com>
Date: Fri, 17 May 2024 11:21:20 -0400
Subject: [PATCH] Switch to rTorrent stickz project

rTorrent stickz is a project maintained by a core collaborator of ruTorrent.
https://github.com/stickz/rtorrent

It includes significant performance and stability optimizations for the rTorrent software. It also ensures full compatibility with link time optimizations during the build process.

It is a drop in replacement for rTorrent with all the core patches from this docker container included. It also improves the implementation of UDNS to make it more reliable.

The rTorrent stickz project focuses on performance and stability of the torrent client. It is designed to be fully compatible with ruTorrent. Also, it does not seek to modify the torrent software in any way that may be undesirable for users. As a result, it is a perfect candidate for this docker container!
---
 Dockerfile                                    |  44 +-
 README.md                                     |   6 +-
 .../libtorrent/libtorrent-scanf-0.13.8.patch  | 216 ------
 .../libtorrent/libtorrent-udns-0.13.8.patch   | 699 ------------------
 patches/libtorrent/throttle-fix-0.13.8.patch  |  35 -
 patches/rtorrent/lockfile-fix.patch           |  28 -
 patches/rtorrent/rtorrent-ml-cg-fix.patch     |  74 --
 patches/rtorrent/rtorrent-ml-cui-fix.patch    |  30 -
 patches/rtorrent/rtorrent-ml-dc-fix.patch     |  23 -
 patches/rtorrent/rtorrent-scrape.patch        |  91 ---
 patches/rtorrent/scgi-fix.patch               |  23 -
 patches/rtorrent/session-file-fix.patch       |  46 --
 patches/rtorrent/xmlrpc-fix.patch             |  32 -
 patches/rtorrent/xmlrpc-logic-fix.patch       |  23 -
 14 files changed, 16 insertions(+), 1354 deletions(-)
 delete mode 100644 patches/libtorrent/libtorrent-scanf-0.13.8.patch
 delete mode 100644 patches/libtorrent/libtorrent-udns-0.13.8.patch
 delete mode 100644 patches/libtorrent/throttle-fix-0.13.8.patch
 delete mode 100644 patches/rtorrent/lockfile-fix.patch
 delete mode 100644 patches/rtorrent/rtorrent-ml-cg-fix.patch
 delete mode 100644 patches/rtorrent/rtorrent-ml-cui-fix.patch
 delete mode 100644 patches/rtorrent/rtorrent-ml-dc-fix.patch
 delete mode 100644 patches/rtorrent/rtorrent-scrape.patch
 delete mode 100644 patches/rtorrent/scgi-fix.patch
 delete mode 100644 patches/rtorrent/session-file-fix.patch
 delete mode 100644 patches/rtorrent/xmlrpc-fix.patch
 delete mode 100644 patches/rtorrent/xmlrpc-logic-fix.patch

diff --git a/Dockerfile b/Dockerfile
index f105ab50..fc28c6fd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,8 +4,6 @@ ARG LIBSIG_VERSION=3.0.3
 ARG CARES_VERSION=1.24.0
 ARG CURL_VERSION=8.5.0
 ARG XMLRPC_VERSION=01.58.00
-ARG LIBTORRENT_VERSION=v0.13.8
-ARG RTORRENT_VERSION=v0.9.8
 ARG MKTORRENT_VERSION=v1.1
 ARG GEOIP2_PHPEXT_VERSION=1.3.1
 
@@ -13,6 +11,9 @@ ARG GEOIP2_PHPEXT_VERSION=1.3.1
 ARG RUTORRENT_VERSION=97b431479022ceb1cc81df6b7e234e606bd3e13c
 ARG GEOIP2_RUTORRENT_VERSION=4ff2bde530bb8eef13af84e4413cedea97eda148
 
+# rTorrent stickz v3
+ARG RTORRENT_STICKZ_VERSION=491dc6ed1ab3744d3b14800c33550564204bc8f5
+
 ARG ALPINE_VERSION=3.19
 ARG ALPINE_S6_VERSION=${ALPINE_VERSION}-2.2.0.3
 
@@ -37,15 +38,10 @@ FROM src AS src-curl
 ARG CURL_VERSION
 RUN curl -sSL "https://curl.se/download/curl-${CURL_VERSION}.tar.gz" | tar xz --strip 1
 
-FROM src AS src-libtorrent
-RUN git init . && git remote add origin "https://github.com/rakshasa/libtorrent.git"
-ARG LIBTORRENT_VERSION
-RUN git fetch origin "${LIBTORRENT_VERSION}" && git checkout -q FETCH_HEAD
-
 FROM src AS src-rtorrent
-RUN git init . && git remote add origin "https://github.com/rakshasa/rtorrent.git"
-ARG RTORRENT_VERSION
-RUN git fetch origin "${RTORRENT_VERSION}" && git checkout -q FETCH_HEAD
+RUN git init . && git remote add origin "https://github.com/stickz/rtorrent.git"
+ARG RTORRENT_STICKZ_VERSION
+RUN git fetch origin "${RTORRENT_STICKZ_VERSION}" && git checkout -q FETCH_HEAD
 
 FROM src AS src-mktorrent
 RUN git init . && git remote add origin "https://github.com/esmil/mktorrent.git"
@@ -90,7 +86,6 @@ RUN apk --update --no-cache add \
     ncurses-dev \
     nghttp2-dev \
     openssl-dev \
-    patch \
     pcre-dev \
     php82-dev \
     php82-pear \
@@ -134,34 +129,21 @@ RUN make install -j$(nproc)
 RUN make DESTDIR=${DIST_PATH} install -j$(nproc)
 RUN tree ${DIST_PATH}
 
-WORKDIR /usr/local/src/libtorrent
-COPY --from=src-libtorrent /src .
-COPY /patches/libtorrent .
-RUN patch -p1 < throttle-fix-0.13.8.patch \
-  && patch -p1 < libtorrent-udns-0.13.8.patch \
-  && patch -p1 < libtorrent-scanf-0.13.8.patch
+WORKDIR /usr/local/src/rtorrent
+COPY --from=src-rtorrent /src .
+
+WORKDIR /usr/local/src/rtorrent/libtorrent
 RUN ./autogen.sh
 RUN ./configure --with-posix-fallocate --enable-aligned
-RUN make -j$(nproc) CXXFLAGS="-w -O3 -flto"
+RUN make -j$(nproc) CXXFLAGS="-w -O3 -flto -Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing"
 RUN make install -j$(nproc)
 RUN make DESTDIR=${DIST_PATH} install -j$(nproc)
 RUN tree ${DIST_PATH}
 
-WORKDIR /usr/local/src/rtorrent
-COPY --from=src-rtorrent /src .
-COPY /patches/rtorrent .
-RUN patch -p1 < lockfile-fix.patch \
-  && patch -p1 < rtorrent-scrape.patch \
-  && patch -p1 < scgi-fix.patch \
-  && patch -p1 < session-file-fix.patch \
-  && patch -p1 < xmlrpc-fix.patch \
-  && patch -p1 < xmlrpc-logic-fix.patch \
-  && patch -p1 < rtorrent-ml-cg-fix.patch \
-  && patch -p1 < rtorrent-ml-cui-fix.patch \
-  && patch -p1 < rtorrent-ml-dc-fix.patch
+WORKDIR /usr/local/src/rtorrent/rtorrent
 RUN ./autogen.sh
 RUN ./configure --with-xmlrpc-c --with-ncurses
-RUN make -j$(nproc) CXXFLAGS="-w -O3 -flto"
+RUN make -j$(nproc) CXXFLAGS="-w -O3 -flto -Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing"
 RUN make install -j$(nproc)
 RUN make DESTDIR=${DIST_PATH} install -j$(nproc)
 RUN tree ${DIST_PATH}
diff --git a/README.md b/README.md
index a9a72d6d..ce46fca4 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,9 @@ ___
 
 * Run as non-root user
 * Multi-platform image
-* Latest [rTorrent](https://github.com/rakshasa/rtorrent) / [libTorrent](https://github.com/rakshasa/libtorrent) release compiled from source
-  * Includes [rTorrent patches](./patches/rtorrent) to increase software stability
-  * Includes [libtorrent patches](./patches/libtorrent) to increase software stability
+* Latest rTorrent and libTorrent from [rTorrent stickz](https://github.com/stickz/rtorrent) project.
+  * Includes significant performance and stability improvements.
+  * Includes compatibility with Link Time Optimizations.
 * Latest [ruTorrent](https://github.com/Novik/ruTorrent) release
 * Domain name resolving enhancements with [c-ares](https://github.com/rakshasa/rtorrent/wiki/Performance-Tuning#rtrorrent-with-c-ares) and [UDNS](https://www.corpit.ru/mjt/udns.html) for asynchronous DNS requests
 * Enhanced [rTorrent config](rootfs/tpls/.rtorrent.rc) and bootstraping with a [local config](rootfs/tpls/etc/rtorrent/.rtlocal.rc)
diff --git a/patches/libtorrent/libtorrent-scanf-0.13.8.patch b/patches/libtorrent/libtorrent-scanf-0.13.8.patch
deleted file mode 100644
index 8683203d..00000000
--- a/patches/libtorrent/libtorrent-scanf-0.13.8.patch
+++ /dev/null
@@ -1,216 +0,0 @@
-From: stickz <stickman002@mail.com>
-Date: Mon, 17 Apr 2023 17:48:59 -0400
-Subject: [PATCH] Add udpTrackerInfo vector
-
----
- rak/tracker_info.h          | 67 +++++++++++++++++++++++++++++++++++++
- src/globals.cc              |  1 +
- src/globals.h               |  3 ++
- src/torrent/tracker_list.cc |  9 +++--
- src/tracker/tracker_udp.cc  | 29 ++++------------
- src/tracker/tracker_udp.h   |  3 +-
- 6 files changed, 86 insertions(+), 26 deletions(-)
- create mode 100644 rak/tracker_info.h
-
-diff --git a/rak/tracker_info.h b/rak/tracker_info.h
-new file mode 100644
-index 000000000..50a03d136
---- /dev/null
-+++ b/rak/tracker_info.h
-@@ -0,0 +1,67 @@
-+#ifndef RAK_TRACKER_H
-+#define RAK_TRACKER_H
-+
-+#include <string>
-+#include <vector>
-+
-+namespace rak {
-+
-+class tracker_info {
-+public:
-+  bool equals (const std::string u) { return url.compare(u) == 0; }
-+
-+  void set (const std::string u, const char* h, const int p) {
-+    url = u;
-+    hostname.assign(h);
-+    port = p;
-+    broken = false;
-+  }
-+  void set_broken() { broken = true; }
-+  bool get_broken() { return broken; }
-+
-+  std::string get_url() { return url; }
-+  std::string get_hostname() { return hostname; }
-+  int get_port() { return port; }
-+
-+private:
-+  bool broken;
-+  std::string url;
-+  std::string hostname;
-+  int port;
-+};
-+
-+class udp_tracker_vector : public std::vector<tracker_info> {
-+public:
-+  tracker_info get_info(const std::string url) {
-+    for (size_t i=0; i<size(); i++) {
-+      if (at(i).equals(url)) {
-+        return at(i);
-+      }
-+    }
-+    return create_info(url);
-+  }
-+
-+private:
-+  typedef std::vector<tracker_info>           base_type;
-+  typedef typename base_type::reference       reference;
-+  using base_type::size;
-+  using base_type::at;
-+
-+  tracker_info create_info(const std::string url) {
-+    char hostname[1024] = {0};
-+    int port;
-+    tracker_info new_info;
-+
-+    if (sscanf(url.c_str(), "udp://%1023[^:]:%i/announce", &hostname, &port) == 2 && hostname[0] != '\0' && port > 0 && port < (1 << 16)) {
-+      new_info.set(url, hostname, port);
-+      base_type::push_back(new_info);
-+      return new_info;
-+    }
-+
-+    new_info.set_broken();
-+    return new_info;
-+  }
-+};
-+}
-+
-+#endif
-diff --git a/src/globals.cc b/src/globals.cc
-index 88130c19c..c33d395a7 100644
---- a/src/globals.cc
-+++ b/src/globals.cc
-@@ -43,5 +43,6 @@ namespace torrent {
- 
- LIBTORRENT_EXPORT rak::priority_queue_default taskScheduler;
- LIBTORRENT_EXPORT rak::timer                  cachedTime;
-+LIBTORRENT_EXPORT rak::udp_tracker_vector     udpTrackerInfo;
- 
- }
-diff --git a/src/globals.h b/src/globals.h
-index 564ac86df..967d0d482 100644
---- a/src/globals.h
-+++ b/src/globals.h
-@@ -39,12 +39,15 @@
- 
- #include <rak/timer.h>
- #include <rak/priority_queue_default.h>
-+#include <rak/tracker_info.h>
- 
- namespace torrent {
- 
- extern rak::priority_queue_default taskScheduler;
- extern rak::timer                  cachedTime;
- 
-+extern rak::udp_tracker_vector udpTrackerInfo;
-+
- }
- 
- #endif
-diff --git a/src/torrent/tracker_list.cc b/src/torrent/tracker_list.cc
-index 625055da7..ee1f2dcef 100644
---- a/src/torrent/tracker_list.cc
-+++ b/src/torrent/tracker_list.cc
-@@ -197,8 +197,13 @@ TrackerList::insert_url(unsigned int group, const std::string& url, bool extra_t
-       std::strncmp("https://", url.c_str(), 8) == 0) {
-     tracker = new TrackerHttp(this, url, flags);
- 
--  } else if (std::strncmp("udp://", url.c_str(), 6) == 0) {
--    tracker = new TrackerUdp(this, url, flags);
-+  } else if (std::strncmp("udp://", url.c_str(), 6) == 0) {    
-+    rak::tracker_info udpInfo = udpTrackerInfo.get_info(url);
-+    if (udpInfo.get_broken()) {
-+       LT_LOG_TRACKER(INFO, "skipped broken tracker (url:%s)", url.c_str());
-+       return;
-+    }    
-+    tracker = new TrackerUdp(this, udpInfo, flags);
- 
-   } else if (std::strncmp("dht://", url.c_str(), 6) == 0 && TrackerDht::is_allowed()) {
-     tracker = new TrackerDht(this, url, flags);
-diff --git a/src/tracker/tracker_udp.cc b/src/tracker/tracker_udp.cc
-index 04d836f49..2ba6ea212 100644
---- a/src/tracker/tracker_udp.cc
-+++ b/src/tracker/tracker_udp.cc
-@@ -65,10 +65,11 @@
- 
- namespace torrent {
- 
--TrackerUdp::TrackerUdp(TrackerList* parent, const std::string& url, int flags) :
--  Tracker(parent, url, flags),
-+TrackerUdp::TrackerUdp(TrackerList* parent, rak::tracker_info& info, int flags) :
-+  Tracker(parent, info.get_url(), flags),
- 
--  m_port(0),
-+  m_port(info.get_port()),
-+  m_hostname(info.get_hostname()),
- 
-   m_readBuffer(NULL),
-   m_writeBuffer(NULL) {
-@@ -93,35 +94,17 @@ TrackerUdp::send_state(int state) {
-   close_directly();
-   m_latest_event = state;
- 
--  hostname_type hostname;
--
--  if (!parse_udp_url(m_url, hostname, m_port))
--    return receive_failed("could not parse hostname or port");
--
--  LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", hostname.data());
-+  LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", m_hostname.c_str());
- 
-   m_sendState = state;
-   m_resolver_query = manager->connection_manager()->async_resolver().enqueue(
--      hostname.data(),
-+      m_hostname.c_str(),
-       AF_UNSPEC,
-       &m_resolver_callback
-   );
-   manager->connection_manager()->async_resolver().flush();
- }
- 
--bool
--TrackerUdp::parse_udp_url(const std::string& url, hostname_type& hostname, int& port) const {
--  if (std::sscanf(m_url.c_str(), "udp://%1023[^:]:%i", hostname.data(), &port) == 2 && hostname[0] != '\0' &&
--      port > 0 && port < (1 << 16))
--    return true;
--
--  if (std::sscanf(m_url.c_str(), "udp://[%1023[^]]]:%i", hostname.data(), &port) == 2 && hostname[0] != '\0' &&
--      port > 0 && port < (1 << 16))
--    return true;
--
--  return false;
--}
--
- void
- TrackerUdp::start_announce(const sockaddr* sa, int err) {
-   m_resolver_query = NULL;
-diff --git a/src/tracker/tracker_udp.h b/src/tracker/tracker_udp.h
-index a5f250a77..480a1e885 100644
---- a/src/tracker/tracker_udp.h
-+++ b/src/tracker/tracker_udp.h
-@@ -58,7 +58,7 @@ class TrackerUdp : public SocketDatagram, public Tracker {
- 
-   static const uint64_t magic_connection_id = 0x0000041727101980ll;
- 
--  TrackerUdp(TrackerList* parent, const std::string& url, int flags);
-+  TrackerUdp(TrackerList* parent, rak::tracker_info& info, int flags);
-   ~TrackerUdp();
-   
-   const char*         type_name() const { return "tracker_udp"; }
-@@ -95,6 +95,7 @@ class TrackerUdp : public SocketDatagram, public Tracker {
- 
-   rak::socket_address m_connectAddress;
-   int                 m_port;
-+  std::string         m_hostname;
- 
-   int                 m_sendState;
- 
diff --git a/patches/libtorrent/libtorrent-udns-0.13.8.patch b/patches/libtorrent/libtorrent-udns-0.13.8.patch
deleted file mode 100644
index d4079c4e..00000000
--- a/patches/libtorrent/libtorrent-udns-0.13.8.patch
+++ /dev/null
@@ -1,699 +0,0 @@
-From: stickz <stickman002@mail.com>
-Date: Mon, 17 Apr 2023 13:10:00 -0400
-Subject: [PATCH] Udns 0.13.8 (#3)
-
----
- configure.ac                      |   2 +
- scripts/udns.m4                   |  26 ++++
- src/torrent/connection_manager.cc |  81 +++++++++++-
- src/torrent/connection_manager.h  |  44 +++++--
- src/tracker/tracker_udp.cc        |  49 ++-----
- src/tracker/tracker_udp.h         |   6 +-
- src/utils/Makefile.am             |   4 +-
- src/utils/udnsevent.cc            | 208 ++++++++++++++++++++++++++++++
- src/utils/udnsevent.h             |  57 ++++++++
- 9 files changed, 423 insertions(+), 54 deletions(-)
- create mode 100644 scripts/udns.m4
- create mode 100644 src/utils/udnsevent.cc
- create mode 100644 src/utils/udnsevent.h
-
-diff --git a/configure.ac b/configure.ac
-index 4ed08124b..ef4a4fab8 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -103,6 +103,8 @@ AC_ARG_ENABLE(cyrus-rc4,
-   ]
- )
- 
-+TORRENT_WITH_UDNS()
-+
- AC_CHECK_FUNCS(posix_memalign)
- 
- TORRENT_CHECK_MADVISE()
-diff --git a/scripts/udns.m4 b/scripts/udns.m4
-new file mode 100644
-index 000000000..fee645c9d
---- /dev/null
-+++ b/scripts/udns.m4
-@@ -0,0 +1,26 @@
-+dnl function for enabling/disabling udns support
-+AC_DEFUN([TORRENT_WITH_UDNS], [
-+  AC_ARG_WITH(
-+    [udns],
-+    AS_HELP_STRING([--without-udns], [Don't use udns, falling back to synchronous DNS resolution.])
-+  )
-+dnl neither ubuntu nor fedora ships a pkgconfig file for udns
-+  AS_IF(
-+    [test "x$with_udns"  != "xno"],
-+    [AC_CHECK_HEADERS([udns.h], [have_udns=yes], [have_udns=no])],
-+    [have_udns=no]
-+  )
-+  AS_IF(
-+    [test "x$have_udns" = "xyes"],
-+    [
-+      AC_DEFINE(USE_UDNS, 1, Define to build with udns support.)
-+      LIBS="$LIBS -ludns"
-+    ],
-+    [
-+      AS_IF(
-+        [test "x$with_udns" = "xyes"],
-+        [AC_MSG_ERROR([udns requested but not found])]
-+      )
-+    ]
-+  )
-+])
-diff --git a/src/torrent/connection_manager.cc b/src/torrent/connection_manager.cc
-index 972dcbfc3..f1a5778a6 100644
---- a/src/torrent/connection_manager.cc
-+++ b/src/torrent/connection_manager.cc
-@@ -48,11 +48,79 @@
- #include "exceptions.h"
- #include "manager.h"
- 
-+#ifdef USE_UDNS
-+#include "utils/udnsevent.h"
-+#endif
-+
- namespace torrent {
- 
--// Fix TrackerUdp, etc, if this is made async.
--static ConnectionManager::slot_resolver_result_type*
--resolve_host(const char* host, int family, int socktype, ConnectionManager::slot_resolver_result_type slot) {
-+AsyncResolver::AsyncResolver(ConnectionManager *) {}
-+
-+#ifdef USE_UDNS
-+class UdnsAsyncResolver : public AsyncResolver {
-+public:
-+  UdnsAsyncResolver(ConnectionManager *cm) : AsyncResolver(cm) {}
-+
-+  void *enqueue(const char *name, int family, resolver_callback *cbck) {
-+    return m_udnsevent.enqueue_resolve(name, family, cbck);
-+  }
-+
-+  void flush() {
-+    m_udnsevent.flush_resolves();
-+  }
-+
-+  void cancel(void *query) {
-+    m_udnsevent.cancel(static_cast<udns_query*>(query));
-+  }
-+
-+protected:
-+  UdnsEvent           m_udnsevent;
-+};
-+#define ASYNC_RESOLVER_IMPL UdnsAsyncResolver
-+#else
-+class StubAsyncResolver : public AsyncResolver {
-+public:
-+  struct mock_resolve {
-+    std::string hostname;
-+    int family;
-+    resolver_callback *callback;
-+  };
-+
-+  StubAsyncResolver(ConnectionManager *cm): AsyncResolver(cm), m_connection_manager(cm) {}
-+
-+  void *enqueue(const char *name, int family, resolver_callback *cbck) {
-+    mock_resolve *mr = new mock_resolve {name, family, cbck};
-+    m_mock_resolve_queue.emplace_back(mr);
-+    return mr;
-+  }
-+
-+  void flush() {
-+    // dequeue all callbacks and resolve them synchronously
-+    while (!m_mock_resolve_queue.empty()) {
-+      std::unique_ptr<mock_resolve> mr = std::move(m_mock_resolve_queue.back());
-+      m_mock_resolve_queue.pop_back();
-+      m_connection_manager->resolver()(mr->hostname.c_str(), mr->family, 0, *(mr->callback));
-+    }
-+  }
-+
-+  void cancel(void *query) {
-+    auto it = std::find(
-+      std::begin(m_mock_resolve_queue),
-+      std::end(m_mock_resolve_queue),
-+      std::unique_ptr<mock_resolve>(static_cast<mock_resolve*>(query))
-+    );
-+    if (it != std::end(m_mock_resolve_queue)) m_mock_resolve_queue.erase(it);
-+  }
-+
-+protected:
-+  ConnectionManager *m_connection_manager;
-+  std::vector<std::unique_ptr<mock_resolve>> m_mock_resolve_queue;
-+};
-+#define ASYNC_RESOLVER_IMPL StubAsyncResolver
-+#endif
-+
-+static void
-+resolve_host(const char* host, int family, int socktype, resolver_callback slot) {
-   if (manager->main_thread_main()->is_current())
-     thread_base::release_global_lock();
- 
-@@ -64,7 +132,7 @@ resolve_host(const char* host, int family, int socktype, ConnectionManager::slot
-       thread_base::acquire_global_lock();
- 
-     slot(NULL, err);
--    return NULL;
-+    return;
-   }
- 
-   rak::socket_address sa;
-@@ -75,7 +143,7 @@ resolve_host(const char* host, int family, int socktype, ConnectionManager::slot
-     thread_base::acquire_global_lock();
-   
-   slot(sa.c_sockaddr(), 0);
--  return NULL;
-+  return;
- }
- 
- ConnectionManager::ConnectionManager() :
-@@ -89,7 +157,8 @@ ConnectionManager::ConnectionManager() :
- 
-   m_listen(new Listen),
-   m_listen_port(0),
--  m_listen_backlog(SOMAXCONN) {
-+  m_listen_backlog(SOMAXCONN),
-+  m_async_resolver(new ASYNC_RESOLVER_IMPL(this)) {
- 
-   m_bindAddress = (new rak::socket_address())->c_sockaddr();
-   m_localAddress = (new rak::socket_address())->c_sockaddr();
-diff --git a/src/torrent/connection_manager.h b/src/torrent/connection_manager.h
-index 2dcf2b379..5b03f110f 100644
---- a/src/torrent/connection_manager.h
-+++ b/src/torrent/connection_manager.h
-@@ -39,12 +39,13 @@
- #ifndef LIBTORRENT_CONNECTION_MANAGER_H
- #define LIBTORRENT_CONNECTION_MANAGER_H
- 
--#include <list>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <netinet/in_systm.h>
- #include <netinet/ip.h>
- #include <sys/socket.h>
-+#include <list>
-+#include <memory>
- #include lt_tr1_functional
- #include <torrent/common.h>
- 
-@@ -54,6 +55,29 @@ namespace torrent {
- // First element is upload throttle, second element is download throttle.
- typedef std::pair<Throttle*, Throttle*> ThrottlePair;
- 
-+// The sockaddr argument in the result call is NULL if the resolve failed,
-+// and the int holds the error code.
-+typedef std::function<void (const sockaddr*, int)> resolver_callback;
-+
-+// Encapsulates whether we do genuine async resolution or fall back to sync.
-+// In a build with USE_UDNS, these do genuine asynchronous DNS resolution.
-+// In a build without it, they're stubbed out to use a synchronous getaddrinfo(3)
-+// call, while exposing the same API.
-+class LIBTORRENT_EXPORT AsyncResolver {
-+public:
-+  AsyncResolver(ConnectionManager *);
-+
-+  // this queues a DNS resolve but doesn't send it. it doesn't execute any callbacks
-+  // and returns control immediately. the return value is an opaque identifier that
-+  // can be used to cancel the query (as long as the callback hasn't been executed yet):
-+  virtual void*   enqueue(const char *name, int family, resolver_callback *cbck) = 0;
-+  // this sends any queued resolves. it can execute arbitrary callbacks
-+  // before returning control:
-+  virtual void    flush() = 0;
-+  // this cancels a pending async query (as long as the callback hasn't executed yet):
-+  virtual void    cancel(void *query) = 0;
-+};
-+
- class LIBTORRENT_EXPORT ConnectionManager {
- public:
-   typedef uint32_t size_type;
-@@ -100,9 +124,7 @@ class LIBTORRENT_EXPORT ConnectionManager {
-   typedef std::function<uint32_t (const sockaddr*)>     slot_filter_type;
-   typedef std::function<ThrottlePair (const sockaddr*)> slot_throttle_type;
- 
--  // The sockaddr argument in the result slot call is NULL if the resolve failed, and the int holds the errno.
--  typedef std::function<void (const sockaddr*, int)> slot_resolver_result_type;
--  typedef std::function<slot_resolver_result_type* (const char*, int, int, slot_resolver_result_type)> slot_resolver_type;
-+  typedef std::function<void (const char*, int, int, resolver_callback)> slot_resolver_type;
- 
-   ConnectionManager();
-   ~ConnectionManager();
-@@ -154,12 +176,16 @@ class LIBTORRENT_EXPORT ConnectionManager {
-   void                set_listen_port(port_type p)            { m_listen_port = p; }
-   void                set_listen_backlog(int v);
- 
--  // The resolver returns a pointer to its copy of the result slot
--  // which the caller may set blocked to prevent the slot from being
--  // called. The pointer must be NULL if the result slot was already
--  // called because the resolve was synchronous.
-+  void*               enqueue_async_resolve(const char *name, int family, resolver_callback *cbck);
-+  void                flush_async_resolves();
-+  void                cancel_async_resolve(void *query);
-+
-+  // Legacy synchronous resolver interface.
-   slot_resolver_type& resolver()          { return m_slot_resolver; }
- 
-+  // Asynchronous resolver interface.
-+  AsyncResolver&      async_resolver()    { return *m_async_resolver; }
-+
-   // The slot returns a ThrottlePair to use for the given address, or
-   // NULLs to use the default throttle.
-   slot_throttle_type& address_throttle()  { return m_slot_address_throttle; }
-@@ -190,6 +216,8 @@ class LIBTORRENT_EXPORT ConnectionManager {
-   slot_filter_type    m_slot_filter;
-   slot_resolver_type  m_slot_resolver;
-   slot_throttle_type  m_slot_address_throttle;
-+
-+  std::unique_ptr<AsyncResolver> m_async_resolver;
- };
- 
- }
-diff --git a/src/tracker/tracker_udp.cc b/src/tracker/tracker_udp.cc
-index 93493e478..04d836f49 100644
---- a/src/tracker/tracker_udp.cc
-+++ b/src/tracker/tracker_udp.cc
-@@ -70,25 +70,22 @@ TrackerUdp::TrackerUdp(TrackerList* parent, const std::string& url, int flags) :
- 
-   m_port(0),
- 
--  m_slot_resolver(NULL),
-   m_readBuffer(NULL),
-   m_writeBuffer(NULL) {
- 
-   m_taskTimeout.slot() = std::bind(&TrackerUdp::receive_timeout, this);
-+
-+  m_resolver_callback = std::bind(&TrackerUdp::start_announce, this, std::placeholders::_1, std::placeholders::_2);
-+  m_resolver_query = NULL;
- }
- 
- TrackerUdp::~TrackerUdp() {
--  if (m_slot_resolver != NULL) {
--    *m_slot_resolver = resolver_type();
--    m_slot_resolver = NULL;
--  }
--
-   close_directly();
- }
-   
- bool
- TrackerUdp::is_busy() const {
--  return get_fd().is_valid();
-+  return (m_resolver_query != NULL) || get_fd().is_valid();
- }
- 
- void
-@@ -104,15 +101,12 @@ TrackerUdp::send_state(int state) {
-   LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", hostname.data());
- 
-   m_sendState = state;
--
--  // Because we can only remember one slot, set any pending resolves blocked
--  // so that if this tracker is deleted, the member function won't be called.
--  if (m_slot_resolver != NULL) {
--    *m_slot_resolver = resolver_type();
--    m_slot_resolver = NULL;
--  }
--
--  m_slot_resolver = make_resolver_slot(hostname);
-+  m_resolver_query = manager->connection_manager()->async_resolver().enqueue(
-+      hostname.data(),
-+      AF_UNSPEC,
-+      &m_resolver_callback
-+  );
-+  manager->connection_manager()->async_resolver().flush();
- }
- 
- bool
-@@ -128,21 +122,9 @@ TrackerUdp::parse_udp_url(const std::string& url, hostname_type& hostname, int&
-   return false;
- }
- 
--TrackerUdp::resolver_type*
--TrackerUdp::make_resolver_slot(const hostname_type& hostname) {
--  return manager->connection_manager()->resolver()(hostname.data(), PF_UNSPEC, SOCK_DGRAM,
--                                                   std::bind(&TrackerUdp::start_announce,
--                                                             this,
--                                                             std::placeholders::_1,
--                                                             std::placeholders::_2));
--}
--
- void
- TrackerUdp::start_announce(const sockaddr* sa, int err) {
--  if (m_slot_resolver != NULL) {
--    *m_slot_resolver = resolver_type();
--    m_slot_resolver = NULL;
--  }
-+  m_resolver_query = NULL;
- 
-   if (sa == NULL)
-     return receive_failed("could not resolve hostname");
-@@ -180,9 +162,6 @@ TrackerUdp::start_announce(const sockaddr* sa, int err) {
- 
- void
- TrackerUdp::close() {
--  if (!get_fd().is_valid())
--    return;
--
-   LT_LOG_TRACKER(DEBUG, "request cancelled (state:%s url:%s)",
-                  option_as_string(OPTION_TRACKER_EVENT, m_latest_event), m_url.c_str());
- 
-@@ -191,9 +170,6 @@ TrackerUdp::close() {
- 
- void
- TrackerUdp::disown() {
--  if (!get_fd().is_valid())
--    return;
--
-   LT_LOG_TRACKER(DEBUG, "request disowned (state:%s url:%s)",
-                  option_as_string(OPTION_TRACKER_EVENT, m_latest_event), m_url.c_str());
- 
-@@ -202,6 +178,9 @@ TrackerUdp::disown() {
- 
- void
- TrackerUdp::close_directly() {
-+  manager->connection_manager()->async_resolver().cancel(m_resolver_query);
-+  m_resolver_query = NULL;
-+
-   if (!get_fd().is_valid())
-     return;
- 
-diff --git a/src/tracker/tracker_udp.h b/src/tracker/tracker_udp.h
-index aaa7ff671..a5f250a77 100644
---- a/src/tracker/tracker_udp.h
-+++ b/src/tracker/tracker_udp.h
-@@ -56,8 +56,6 @@ class TrackerUdp : public SocketDatagram, public Tracker {
-   typedef ProtocolBuffer<512> ReadBuffer;
-   typedef ProtocolBuffer<512> WriteBuffer;
- 
--  typedef ConnectionManager::slot_resolver_result_type resolver_type;
--
-   static const uint64_t magic_connection_id = 0x0000041727101980ll;
- 
-   TrackerUdp(TrackerList* parent, const std::string& url, int flags);
-@@ -94,14 +92,14 @@ class TrackerUdp : public SocketDatagram, public Tracker {
-   bool                process_error_output();
- 
-   bool                parse_udp_url(const std::string& url, hostname_type& hostname, int& port) const;
--  resolver_type*      make_resolver_slot(const hostname_type& hostname);
- 
-   rak::socket_address m_connectAddress;
-   int                 m_port;
- 
-   int                 m_sendState;
- 
--  resolver_type*      m_slot_resolver;
-+  resolver_callback   m_resolver_callback;
-+  void*               m_resolver_query;
- 
-   uint32_t            m_action;
-   uint64_t            m_connectionId;
-diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am
-index 27ce359b1..d148ff798 100644
---- a/src/utils/Makefile.am
-+++ b/src/utils/Makefile.am
-@@ -9,6 +9,8 @@ libsub_utils_la_SOURCES = \
- 	sha1.h \
- 	sha_fast.cc \
- 	sha_fast.h \
--	queue_buckets.h
-+	queue_buckets.h \
-+	udnsevent.cc \
-+	udnsevent.h
- 
- AM_CPPFLAGS = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir)
-diff --git a/src/utils/udnsevent.cc b/src/utils/udnsevent.cc
-new file mode 100644
-index 000000000..b53d32e8a
---- /dev/null
-+++ b/src/utils/udnsevent.cc
-@@ -0,0 +1,208 @@
-+#include "config.h"
-+#ifdef USE_UDNS
-+
-+#include <netdb.h>
-+#include <netinet/in.h>
-+#include <sys/socket.h>
-+
-+#include <udns.h>
-+
-+#include <torrent/common.h>
-+#include "udnsevent.h"
-+#include "globals.h"
-+#include "manager.h"
-+#include "torrent/poll.h"
-+
-+namespace torrent {
-+
-+int udnserror_to_gaierror(int udnserror) {
-+  switch (udnserror) {
-+    case DNS_E_TEMPFAIL:
-+      return EAI_AGAIN;
-+    case DNS_E_PROTOCOL:
-+      // this isn't quite right
-+      return EAI_FAIL;
-+    case DNS_E_NXDOMAIN:
-+      return EAI_NONAME;
-+    case DNS_E_NODATA:
-+      return EAI_ADDRFAMILY;
-+    case DNS_E_NOMEM:
-+      return EAI_MEMORY;
-+    case DNS_E_BADQUERY:
-+      return EAI_NONAME;
-+    default:
-+      return EAI_ADDRFAMILY;
-+  }
-+}
-+
-+// Compatibility layers so udns can call std::function callbacks.
-+
-+void a4_callback_wrapper(struct ::dns_ctx *ctx, ::dns_rr_a4 *result, void *data) {
-+  struct sockaddr_in sa;
-+  udns_query *query = static_cast<udns_query*>(data);
-+  // udns will free the a4_query after this callback exits
-+  query->a4_query = NULL;
-+
-+  if (result == NULL || result->dnsa4_nrr == 0) {
-+    if (query->a6_query == NULL) {
-+      // nothing more to do: call the callback with a failure status
-+      (*(query->callback))(NULL, udnserror_to_gaierror(::dns_status(ctx)));
-+      delete query;
-+    }
-+    // else: return and wait to see if we get an a6 response
-+  } else {
-+    sa.sin_family = AF_INET;
-+    sa.sin_port = 0;
-+    sa.sin_addr = result->dnsa4_addr[0];
-+    if (query->a6_query != NULL) {
-+      ::dns_cancel(ctx, query->a6_query);
-+    }
-+    (*query->callback)(reinterpret_cast<sockaddr*>(&sa), 0);
-+    delete query;
-+  }
-+}
-+
-+void a6_callback_wrapper(struct ::dns_ctx *ctx, ::dns_rr_a6 *result, void *data) {
-+  struct sockaddr_in6 sa;
-+  udns_query *query = static_cast<udns_query*>(data);
-+  // udns will free the a6_query after this callback exits
-+  query->a6_query = NULL;
-+
-+  if (result == NULL || result->dnsa6_nrr == 0) {
-+    if (query->a4_query == NULL) {
-+      // nothing more to do: call the callback with a failure status
-+      (*(query->callback))(NULL, udnserror_to_gaierror(::dns_status(ctx)));
-+      delete query;
-+    }
-+    // else: return and wait to see if we get an a6 response
-+  } else {
-+    sa.sin6_family = AF_INET6;
-+    sa.sin6_port = 0;
-+    sa.sin6_addr = result->dnsa6_addr[0];
-+    if (query->a4_query != NULL) {
-+      ::dns_cancel(ctx, query->a4_query);
-+    }
-+    (*query->callback)(reinterpret_cast<sockaddr*>(&sa), 0);
-+    delete query;
-+  }
-+}
-+
-+
-+UdnsEvent::UdnsEvent() {
-+  // reinitialize the default context, no-op
-+  // TODO don't do this here --- do it once in the manager, or in rtorrent
-+  ::dns_init(NULL, 0);
-+  // thread-safe context isolated to this object:
-+  m_ctx = ::dns_new(NULL);
-+  m_fileDesc = ::dns_open(m_ctx);
-+  if (m_fileDesc == -1) throw internal_error("dns_init failed");
-+
-+  m_taskTimeout.slot() = std::bind(&UdnsEvent::process_timeouts, this);
-+}
-+
-+UdnsEvent::~UdnsEvent() {
-+  priority_queue_erase(&taskScheduler, &m_taskTimeout);
-+  ::dns_close(m_ctx);
-+  ::dns_free(m_ctx);
-+  m_fileDesc = -1;
-+
-+  for (auto it = std::begin(m_malformed_queries); it != std::end(m_malformed_queries); ++it) {
-+    delete *it;
-+  }
-+}
-+
-+void UdnsEvent::event_read() {
-+  ::dns_ioevent(m_ctx, 0);
-+}
-+
-+void UdnsEvent::event_write() {
-+}
-+
-+void UdnsEvent::event_error() {
-+}
-+
-+struct udns_query *UdnsEvent::enqueue_resolve(const char *name, int family, resolver_callback *callback) {
-+  struct udns_query *query = new udns_query { NULL, NULL, callback, 0 };
-+
-+  if (family == AF_INET || family == AF_UNSPEC) {
-+    query->a4_query = ::dns_submit_a4(m_ctx, name, 0, a4_callback_wrapper, query);
-+    if (query->a4_query == NULL) {
-+      // XXX udns does query parsing up front and will fail immediately
-+      // during submission of malformed domain names, e.g., `..`. In order to
-+      // maintain a clean interface, keep track of this query internally
-+      // so we can call the callback later with a failure code
-+      if (::dns_status(m_ctx) == DNS_E_BADQUERY) {
-+        // this is what getaddrinfo(3) would return:
-+        query->error = EAI_NONAME;
-+        m_malformed_queries.push_back(query);
-+        return query;
-+      } else {
-+        // unrecoverable errors, like ENOMEM
-+        throw new internal_error("dns_submit_a4 failed");
-+      }
-+    }
-+  }
-+
-+  if (family == AF_INET6) {
-+    query->a6_query = ::dns_submit_a6(m_ctx, name, 0, a6_callback_wrapper, query);
-+    if (query->a6_query == NULL) {
-+      // it should be impossible for dns_submit_a6 to fail if dns_submit_a4
-+      // succeeded, but just in case, make it a hard failure:
-+      if (::dns_status(m_ctx) == DNS_E_BADQUERY && query->a4_query == NULL) {
-+        query->error = EAI_NONAME;
-+        m_malformed_queries.push_back(query);
-+        return query;
-+      } else {
-+        throw new internal_error("dns_submit_a6 failed");
-+      }
-+    }
-+  }
-+
-+  return query;
-+}
-+
-+void UdnsEvent::flush_resolves() {
-+  // first process any queries that were malformed
-+  while (!m_malformed_queries.empty()) {
-+    udns_query *query = m_malformed_queries.back();
-+    m_malformed_queries.pop_back();
-+    (*(query->callback))(NULL, query->error);
-+    delete query;
-+  }
-+  process_timeouts();
-+}
-+
-+void UdnsEvent::process_timeouts() {
-+  int timeout = ::dns_timeouts(m_ctx, -1, 0);
-+  if (timeout == -1) {
-+    // no pending queries
-+    manager->poll()->remove_read(this);
-+    manager->poll()->remove_error(this);
-+  } else {
-+    manager->poll()->insert_read(this);
-+    manager->poll()->insert_error(this);
-+    priority_queue_erase(&taskScheduler, &m_taskTimeout);
-+    priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime + rak::timer::from_seconds(timeout)).round_seconds());
-+  }
-+}
-+
-+void UdnsEvent::cancel(struct udns_query *query) {
-+  if (query == NULL) return;
-+
-+  if (query->a4_query != NULL) ::dns_cancel(m_ctx, query->a4_query);
-+
-+  if (query->a6_query != NULL) ::dns_cancel(m_ctx, query->a6_query);
-+
-+  auto it = std::find(std::begin(m_malformed_queries), std::end(m_malformed_queries), query);
-+  if (it != std::end(m_malformed_queries)) m_malformed_queries.erase(it);
-+
-+  delete query;
-+}
-+
-+const char *UdnsEvent::type_name() {
-+  return "UdnsEvent";
-+}
-+
-+}
-+
-+#endif
-diff --git a/src/utils/udnsevent.h b/src/utils/udnsevent.h
-new file mode 100644
-index 000000000..f214814e2
---- /dev/null
-+++ b/src/utils/udnsevent.h
-@@ -0,0 +1,57 @@
-+#ifndef LIBTORRENT_NET_UDNSEVENT_H
-+#define LIBTORRENT_NET_UDNSEVENT_H
-+
-+#include lt_tr1_functional
-+
-+#include <list>
-+#include <inttypes.h>
-+
-+#include <rak/priority_queue_default.h>
-+#include "torrent/event.h"
-+#include "torrent/connection_manager.h"
-+
-+struct dns_ctx;
-+struct dns_query;
-+
-+namespace torrent {
-+
-+struct udns_query {
-+    ::dns_query *a4_query;
-+    ::dns_query *a6_query;
-+    resolver_callback  *callback;
-+    int                 error;
-+};
-+
-+class UdnsEvent : public Event {
-+public:
-+
-+  typedef std::vector<udns_query*> query_list_type;
-+
-+  UdnsEvent();
-+  ~UdnsEvent();
-+
-+  virtual void        event_read();
-+  virtual void        event_write();
-+  virtual void        event_error();
-+  virtual const char* type_name();
-+
-+  // wraps udns's dns_submit_a[46] functions. they and it return control immediately,
-+  // without either sending outgoing UDP packets or executing callbacks:
-+  udns_query*         enqueue_resolve(const char *name, int family, resolver_callback *callback);
-+  // wraps the dns_timeouts function. it sends packets and can execute arbitrary
-+  // callbacks:
-+  void                flush_resolves();
-+  // wraps the dns_cancel function:
-+  void                cancel(udns_query *query);
-+
-+protected:
-+  void                process_timeouts();
-+
-+  ::dns_ctx*             m_ctx;
-+  rak::priority_item     m_taskTimeout;
-+  query_list_type        m_malformed_queries;
-+};
-+
-+}
-+
-+#endif
diff --git a/patches/libtorrent/throttle-fix-0.13.8.patch b/patches/libtorrent/throttle-fix-0.13.8.patch
deleted file mode 100644
index 5a146f0a..00000000
--- a/patches/libtorrent/throttle-fix-0.13.8.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 326598abe30a82f8f74794788e11228300a36164 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Fri, 24 Feb 2023 12:57:59 -0500
-Subject: [PATCH] Allow 10 gigabit speed throttles
-
-This commit increases max upload and download speed by 16 times. It allows utilization of 10 gigabit connections.
----
- src/torrent/download/resource_manager.cc | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/src/torrent/download/resource_manager.cc b/src/torrent/download/resource_manager.cc
-index 8ca7b02e..f882a202 100644
---- a/src/torrent/download/resource_manager.cc
-+++ b/src/torrent/download/resource_manager.cc
-@@ -266,16 +266,16 @@ ResourceManager::set_group(iterator itr, uint16_t grp) {
- 
- void
- ResourceManager::set_max_upload_unchoked(unsigned int m) {
--  if (m > (1 << 16))
--    throw input_error("Max unchoked must be between 0 and 2^16.");
-+  if (m > (1 << 20))
-+    throw input_error("Max unchoked must be between 0 and 2^20.");
- 
-   m_maxUploadUnchoked = m;
- }
- 
- void
- ResourceManager::set_max_download_unchoked(unsigned int m) {
--  if (m > (1 << 16))
--    throw input_error("Max unchoked must be between 0 and 2^16.");
-+  if (m > (1 << 20))
-+    throw input_error("Max unchoked must be between 0 and 2^20.");
- 
-   m_maxDownloadUnchoked = m;
- }
diff --git a/patches/rtorrent/lockfile-fix.patch b/patches/rtorrent/lockfile-fix.patch
deleted file mode 100644
index 648d4505..00000000
--- a/patches/rtorrent/lockfile-fix.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 812bba81bc049a5f786282b3654cab294b0ef236 Mon Sep 17 00:00:00 2001
-From: Aleksa Sarai <cyphar@cyphar.com>
-Date: Mon, 20 Jun 2022 19:09:57 +1000
-Subject: [PATCH] utils: lockfile: avoid stack overflow for lockfile buffer
-
-There appears to have been some change on openSUSE (likely some new
-hardening flags for builds, or some glibc hardening) such that incorrect
-buffer handling results in a segfault even if the buffer is never
-overflowed.
-
-Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
----
- src/utils/lockfile.cc | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/src/utils/lockfile.cc b/src/utils/lockfile.cc
-index 7d11d8c99..fac5cb23e 100644
---- a/src/utils/lockfile.cc
-+++ b/src/utils/lockfile.cc
-@@ -98,7 +98,8 @@ Lockfile::try_lock() {
-   int pos = ::gethostname(buf, 255);
- 
-   if (pos == 0) {
--    ::snprintf(buf + std::strlen(buf), 255, ":+%i\n", ::getpid());
-+    ssize_t len = std::strlen(buf);
-+    ::snprintf(buf + len, 255 - len, ":+%i\n", ::getpid());
-     int __UNUSED result = ::write(fd, buf, std::strlen(buf));
-   }
diff --git a/patches/rtorrent/rtorrent-ml-cg-fix.patch b/patches/rtorrent/rtorrent-ml-cg-fix.patch
deleted file mode 100644
index 24e4459b..00000000
--- a/patches/rtorrent/rtorrent-ml-cg-fix.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 4f57db8b499b672af19243e159d8c3439e1a5b82 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 30 Dec 2023 07:04:47 -0500
-Subject: [PATCH] Resolve choke groups memory leak
-
-Resolves a memory leak during software initialization with choke groups.
----
- src/command_groups.cc  | 11 +++++++++++
- src/command_helpers.cc |  6 ++++++
- src/command_helpers.h  |  1 +
- src/main.cc            |  2 ++
- 4 files changed, 20 insertions(+)
-
-diff --git a/src/command_groups.cc b/src/command_groups.cc
-index 359a532e5..a0bc40dad 100644
---- a/src/command_groups.cc
-+++ b/src/command_groups.cc
-@@ -381,3 +381,14 @@ initialize_command_groups() {
-                                                                  std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::down_queue))));
-   CMD2_ANY_LIST    ("choke_group.down.heuristics.set", std::bind(&apply_cg_heuristics_set, std::placeholders::_2, false));
- }
-+
-+void cleanup_command_groups() {
-+#if USE_CHOKE_GROUP
-+#else
-+  while (!cg_list_hack.empty()) {
-+    auto cg = cg_list_hack.back();
-+    delete cg;
-+    cg_list_hack.pop_back();
-+  }
-+#endif
-+}
-diff --git a/src/command_helpers.cc b/src/command_helpers.cc
-index 54c0b35e4..31599e265 100644
---- a/src/command_helpers.cc
-+++ b/src/command_helpers.cc
-@@ -57,6 +57,12 @@ void initialize_command_tracker();
- void initialize_command_scheduler();
- void initialize_command_ui();
- 
-+void cleanup_command_groups();
-+
-+void cleanup_commands() {
-+  cleanup_command_groups();
-+}
-+
- void
- initialize_commands() {
-   initialize_command_dynamic();
-diff --git a/src/command_helpers.h b/src/command_helpers.h
-index a104fbbc4..48e7ea258 100644
---- a/src/command_helpers.h
-+++ b/src/command_helpers.h
-@@ -42,6 +42,7 @@
- #include "rpc/object_storage.h"
- 
- void initialize_commands();
-+void cleanup_commands();
- 
- //
- // New std::function based command_base helper functions:
-diff --git a/src/main.cc b/src/main.cc
-index c76558f8f..6f06da3c5 100644
---- a/src/main.cc
-+++ b/src/main.cc
-@@ -510,6 +510,8 @@ main(int argc, char** argv) {
-     lt_log_print(torrent::LOG_CRITICAL, "Caught exception: '%s'.", e.what());
-     return -1;
-   }
-+  
-+  cleanup_commands();
- 
-   torrent::log_cleanup();
- 
diff --git a/patches/rtorrent/rtorrent-ml-cui-fix.patch b/patches/rtorrent/rtorrent-ml-cui-fix.patch
deleted file mode 100644
index 9c46e6f6..00000000
--- a/patches/rtorrent/rtorrent-ml-cui-fix.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 993155c5461164f9eca73c43091865ff32906601 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 30 Dec 2023 06:57:13 -0500
-Subject: [PATCH] Fix curses ui memory leak
-
-Resolves a potential memory leak with the curses UI when filtering torrents
----
- src/ui/download_list.cc | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc
-index f1d6af5c6..7cb0e9a89 100644
---- a/src/ui/download_list.cc
-+++ b/src/ui/download_list.cc
-@@ -272,6 +272,7 @@ DownloadList::receive_view_input(Input type) {
-           std::getline(ss, view_name_var, ',');
-           if (current_view()->name() == rak::trim(view_name_var)) {
-               control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered.");
-+              delete input;
-               return;
-           }
-       }
-@@ -281,6 +282,7 @@ DownloadList::receive_view_input(Input type) {
-     break;
- 
-   default:
-+    delete input;
-     throw torrent::internal_error("DownloadList::receive_view_input(...) Invalid input type.");
-   }
- 
diff --git a/patches/rtorrent/rtorrent-ml-dc-fix.patch b/patches/rtorrent/rtorrent-ml-dc-fix.patch
deleted file mode 100644
index debfcc68..00000000
--- a/patches/rtorrent/rtorrent-ml-dc-fix.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 7930792e0bd86a1c3d6469f63a3fa0cc39cc0f8f Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 30 Dec 2023 06:59:53 -0500
-Subject: [PATCH] Fix dynamic commands memory leak
-
-Resolves a memory leak with dynamic commands in the .rtorrent.rc file.
----
- src/command_dynamic.cc | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/command_dynamic.cc b/src/command_dynamic.cc
-index a8d0ff02f..3d7a123b1 100644
---- a/src/command_dynamic.cc
-+++ b/src/command_dynamic.cc
-@@ -147,7 +147,7 @@ system_method_insert_object(const torrent::Object::list_type& args, int flags) {
-     throw torrent::input_error("Invalid type.");
-   }
- 
--  int cmd_flags = 0;
-+  int cmd_flags = rpc::CommandMap::flag_delete_key;
- 
-   if (!(flags & rpc::object_storage::flag_static))
-     cmd_flags |= rpc::CommandMap::flag_modifiable;
diff --git a/patches/rtorrent/rtorrent-scrape.patch b/patches/rtorrent/rtorrent-scrape.patch
deleted file mode 100644
index cfaa6d1a..00000000
--- a/patches/rtorrent/rtorrent-scrape.patch
+++ /dev/null
@@ -1,91 +0,0 @@
-From b68c3b5f8d15a91955f90c1bdd24a0708a2fed2e Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Mon, 17 Apr 2023 19:08:21 -0400
-Subject: [PATCH] Disable forced tracker startup scrape
-
----
- src/command_tracker.cc       | 1 +
- src/core/download_factory.cc | 7 ++++++-
- src/core/download_factory.h  | 4 ++++
- src/main.cc                  | 2 ++
- 4 files changed, 13 insertions(+), 1 deletion(-)
-
-diff --git a/src/command_tracker.cc b/src/command_tracker.cc
-index 30c56eec7..e06947610 100644
---- a/src/command_tracker.cc
-+++ b/src/command_tracker.cc
-@@ -159,6 +159,7 @@ initialize_command_tracker() {
-   CMD2_ANY_VALUE      ("trackers.disable",    std::bind(&apply_enable_trackers, int64_t(0)));
-   CMD2_VAR_VALUE      ("trackers.numwant",    -1);
-   CMD2_VAR_BOOL       ("trackers.use_udp",    true);
-+  CMD2_VAR_BOOL       ("trackers.delay_scrape", true);
- 
-   CMD2_ANY_STRING_V   ("dht.mode.set",          std::bind(&core::DhtManager::set_mode, control->dht_manager(), std::placeholders::_2));
-   CMD2_VAR_VALUE      ("dht.port",              int64_t(6881));
-diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc
-index b1bdd64b9..2d2ea5a3b 100644
---- a/src/core/download_factory.cc
-+++ b/src/core/download_factory.cc
-@@ -105,7 +105,8 @@ DownloadFactory::DownloadFactory(Manager* m) :
-   m_session(false),
-   m_start(false),
-   m_printLog(true),
--  m_isFile(false) {
-+  m_isFile(false),
-+  m_initLoad(false) {
- 
-   m_taskLoad.slot() = std::bind(&DownloadFactory::receive_load, this);
-   m_taskCommit.slot() = std::bind(&DownloadFactory::receive_commit, this);
-@@ -274,6 +275,10 @@ DownloadFactory::receive_success() {
- 
-   if (!rpc::call_command_value("trackers.use_udp"))
-     download->enable_udp_trackers(false);
-+    
-+  // Skip forcing trackers to scrape when rtorrent starts
-+  if (m_initLoad && rpc::call_command_value("trackers.delay_scrape"))
-+    download->set_resume_flags(torrent::Download::start_skip_tracker);
- 
-   // Check first if we already have these values set in the session
-   // torrent, so that it is safe to change the values.
-diff --git a/src/core/download_factory.h b/src/core/download_factory.h
-index 01695e882..29fa9760b 100644
---- a/src/core/download_factory.h
-+++ b/src/core/download_factory.h
-@@ -76,6 +76,9 @@ class DownloadFactory {
- 
-   bool                get_start() const     { return m_start; }
-   void                set_start(bool v)     { m_start = v; }
-+  
-+  bool                get_init_load() const { return m_initLoad; }
-+  void                set_init_load(bool v) { m_initLoad = v; }
- 
-   bool                print_log() const     { return m_printLog; }
-   void                set_print_log(bool v) { m_printLog = v; }
-@@ -105,6 +108,7 @@ class DownloadFactory {
-   bool                m_start;
-   bool                m_printLog;
-   bool                m_isFile;
-+  bool                m_initLoad;
- 
-   command_list_type         m_commands;
-   torrent::Object::map_type m_variables;
-diff --git a/src/main.cc b/src/main.cc
-index 6be6a4dee..c76558f8f 100644
---- a/src/main.cc
-+++ b/src/main.cc
-@@ -132,6 +132,7 @@ load_session_torrents() {
- 
-     // Replace with session torrent flag.
-     f->set_session(true);
-+    f->set_init_load(true);
-     f->slot_finished(std::bind(&rak::call_delete_func<core::DownloadFactory>, f));
-     f->load(entries.path() + first->d_name);
-     f->commit();
-@@ -146,6 +147,7 @@ load_arg_torrents(char** first, char** last) {
- 
-     // Replace with session torrent flag.
-     f->set_start(true);
-+    f->set_init_load(true);
-     f->slot_finished(std::bind(&rak::call_delete_func<core::DownloadFactory>, f));
-     f->load(*first);
-     f->commit();
diff --git a/patches/rtorrent/scgi-fix.patch b/patches/rtorrent/scgi-fix.patch
deleted file mode 100644
index ed44b645..00000000
--- a/patches/rtorrent/scgi-fix.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From f600ba802201da20834751412faafa374ce255c4 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 30 Sep 2023 08:36:15 -0400
-Subject: [PATCH] Increase maximum SCGI request to 16MB
-
-Fixing a problem where xmlrpc-c information fails to update if user is running too many torrents.
----
- src/rpc/scgi_task.h | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/rpc/scgi_task.h b/src/rpc/scgi_task.h
-index e75e9e1ea..a42349bf8 100644
---- a/src/rpc/scgi_task.h
-+++ b/src/rpc/scgi_task.h
-@@ -51,7 +51,7 @@ class SCgiTask : public torrent::Event {
- public:
-   static const unsigned int default_buffer_size = 2047;
-   static const          int max_header_size     = 2000;
--  static const          int max_content_size    = (2 << 20);
-+  static const          int max_content_size    = (2 << 23);
- 
-   SCgiTask() { m_fileDesc = -1; }
- 
diff --git a/patches/rtorrent/session-file-fix.patch b/patches/rtorrent/session-file-fix.patch
deleted file mode 100644
index 91b67e3a..00000000
--- a/patches/rtorrent/session-file-fix.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 7594fc942ecd0ed64f7feea24bd54b6fdddba49b Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 30 Sep 2023 10:34:07 -0400
-Subject: [PATCH] Fix saving session files
-
-Resolves a data corruption issue with torrent session during a power loss.
----
- src/core/download_store.cc | 11 +++++++++++
- 1 file changed, 11 insertions(+)
-
-diff --git a/src/core/download_store.cc b/src/core/download_store.cc
-index 536dba10a..e111b41bc 100644
---- a/src/core/download_store.cc
-+++ b/src/core/download_store.cc
-@@ -40,6 +40,7 @@
- 
- #include <fstream>
- #include <stdio.h>
-+#include <fcntl.h>
- #include <unistd.h>
- #include <rak/error_number.h>
- #include <rak/path.h>
-@@ -102,6 +103,7 @@ bool
- DownloadStore::write_bencode(const std::string& filename, const torrent::Object& obj, uint32_t skip_mask) {
-   torrent::Object tmp;
-   std::fstream output(filename.c_str(), std::ios::out | std::ios::trunc);
-+  int fd = -1;
- 
-   if (!output.is_open())
-     goto download_store_save_error;
-@@ -121,6 +123,15 @@ DownloadStore::write_bencode(const std::string& filename, const torrent::Object&
-     goto download_store_save_error;
- 
-   output.close();
-+  
-+  // Ensure that the new file is actually written to the disk
-+  fd = ::open(filename.c_str(), O_WRONLY);
-+  if (fd < 0)
-+    goto download_store_save_error;
-+
-+  fsync(fd);
-+  ::close(fd);
-+
-   return true;
- 
- download_store_save_error:
diff --git a/patches/rtorrent/xmlrpc-fix.patch b/patches/rtorrent/xmlrpc-fix.patch
deleted file mode 100644
index e33e11bf..00000000
--- a/patches/rtorrent/xmlrpc-fix.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From 4abaa260ce758accc1866d1b0f744dc370ba3254 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Sat, 27 Nov 2021 23:00:20 -0500
-Subject: [PATCH] Fix common rtorrent xml-rpc crash when trying to queue an invalid task
-
-Instead of throwing an internal error and terminating the client, it's better not to queue the invalid task in the first place.
-`C Caught internal_error: 'priority_queue_insert(...) called on an invalid item.'.`
----
- src/rpc/command_scheduler_item.cc | 6 +++++-
- 1 file changed, 5 insertions(+), 1 deletion(-)
-
-diff --git a/src/rpc/command_scheduler_item.cc b/src/rpc/command_scheduler_item.cc
-index 42a6ef43..af04a884 100644
---- a/src/rpc/command_scheduler_item.cc
-+++ b/src/rpc/command_scheduler_item.cc
-@@ -53,10 +53,14 @@ CommandSchedulerItem::enable(rak::timer t) {
- 
-   if (is_queued())
-     disable();
-+    
-+  // Don't schedule invalid tasks for rpc commands
-+  if (!m_task.is_valid())
-+    return;
- 
-   // If 'first' is zero then we execute the task
-   // immediately. ''interval()'' will not return zero so we never end
--  // up in an infinit loop.
-+  // up in an infinite loop.
-   m_timeScheduled = t;
-   priority_queue_insert(&taskScheduler, &m_task, t);
- }
-
diff --git a/patches/rtorrent/xmlrpc-logic-fix.patch b/patches/rtorrent/xmlrpc-logic-fix.patch
deleted file mode 100644
index 97f83c28..00000000
--- a/patches/rtorrent/xmlrpc-logic-fix.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 898d0b21792c9f021a098961c6d47e07a4b188f1 Mon Sep 17 00:00:00 2001
-From: stickz <stickman002@mail.com>
-Date: Mon, 2 Oct 2023 17:02:06 -0400
-Subject: [PATCH] Resolve xmlrpc logic crash
-
-Resolves a rtorrent crash caused by sending invalid xmlrpc logic.
----
- src/rpc/command_impl.h | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/rpc/command_impl.h b/src/rpc/command_impl.h
-index b7ec9168c..a7838d49c 100644
---- a/src/rpc/command_impl.h
-+++ b/src/rpc/command_impl.h
-@@ -63,7 +63,7 @@ template <> struct target_type_id<core::Download*, core::Download*> { static con
- template <> inline bool
- is_target_compatible<target_type>(const target_type& target) { return true; }
- template <> inline bool
--is_target_compatible<torrent::File*>(const target_type& target) { return target.first == command_base::target_file || command_base::target_file_itr; }
-+is_target_compatible<torrent::File*>(const target_type& target) { return (target.first == command_base::target_file || command_base::target_file_itr) && target.first == target_type_id<torrent::File*>::value; }
- 
- template <> inline target_type
- get_target_cast<target_type>(target_type target, int type) { return target; }