diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8f7cfdcb2b..add6ebbe4d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -345,6 +345,7 @@ add_files(
picker_gui.h
progress.cpp
progress.h
+ provider_manager.h
querystring_gui.h
rail.cpp
rail.h
diff --git a/src/provider_manager.h b/src/provider_manager.h
new file mode 100644
index 0000000000..44caa03963
--- /dev/null
+++ b/src/provider_manager.h
@@ -0,0 +1,107 @@
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file provider_manager.h Definition of the ProviderManager */
+
+#ifndef PROVIDER_MANAGER_H
+#define PROVIDER_MANAGER_H
+
+/**
+ * The ProviderManager manages a single Provider-type.
+ *
+ * It allows for automatic registration and unregistration of providers.
+ *
+ * @tparam T Service provider type.
+ */
+template
+class ProviderManager {
+public:
+ /* Avoid copying this object; it is a singleton object. */
+ ProviderManager(ProviderManager const &) = delete;
+
+ ProviderManager &operator=(ProviderManager const &) = delete;
+
+ static void Register(TProviderType &instance)
+ {
+ /* Insert according to comparator. */
+ auto &providers = GetProviders();
+ auto it = std::ranges::lower_bound(providers, &instance, typename TProviderType::ProviderSorter());
+ providers.insert(it, &instance);
+ }
+
+ static void Unregister(TProviderType &instance)
+ {
+ auto &providers = GetProviders();
+ providers.erase(std::find(std::begin(providers), std::end(providers), &instance));
+ }
+
+ /**
+ * Get the currently known sound loaders.
+ * @return The known sound loaders.
+ */
+ static std::vector &GetProviders()
+ {
+ static std::vector providers{};
+ return providers;
+ }
+};
+
+template
+class BaseProvider {
+public:
+ constexpr BaseProvider(std::string_view name, std::string_view description) : name(name), description(description) {}
+ virtual ~BaseProvider() {}
+
+ inline std::string_view GetName() const { return this->name; }
+ inline std::string_view GetDescription() const { return this->description; }
+
+ /**
+ * Sorter for BaseProvider.
+ *
+ * It will sort based on the name. If the name is the
+ * same, it will sort based on the pointer value.
+ */
+ struct ProviderSorter {
+ bool operator()(const T *a, const T *b) const
+ {
+ if (a->GetName() == b->GetName()) return a < b;
+ return a->GetName() < b->GetName();
+ }
+ };
+
+protected:
+ const std::string_view name;
+ const std::string_view description;
+};
+
+template
+class PriorityBaseProvider : public BaseProvider {
+public:
+ constexpr PriorityBaseProvider(std::string_view name, std::string_view description, int priority) : BaseProvider(name, description), priority(priority) {}
+ virtual ~PriorityBaseProvider() {}
+
+ inline int GetPriority() const { return this->priority; }
+
+ /**
+ * Sorter for PriorityBaseProvider.
+ *
+ * It will sort based on the priority, smaller first. If the priority is the
+ * same, it will sort based on the pointer value.
+ */
+ struct ProviderSorter {
+ bool operator()(const T *a, const T *b) const
+ {
+ if (a->GetPriority() == b->GetPriority()) return a < b;
+ return a->GetPriority() < b->GetPriority();
+ }
+ };
+
+protected:
+ const int priority;
+};
+
+#endif /* PROVIDER_MANAGER_H */