diff --git a/source/ps/memory/AllocatorAdapter.h b/source/ps/memory/AllocatorAdapter.h
new file mode 100644
index 0000000000..5d7652f1bb
--- /dev/null
+++ b/source/ps/memory/AllocatorAdapter.h
@@ -0,0 +1,89 @@
+/* Copyright (C) 2026 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_PS_ALLOCATORADAPTER
+#define INCLUDED_PS_ALLOCATORADAPTER
+
+#include
+#include
+#include
+
+namespace PS::Memory
+{
+
+/**
+ * Provides a way to use untyped allocators like LinearAllocator for
+ * containers. This allocator adapter must not outlive the target
+ * allocator. A client must not mix adapters with different
+ * allocators.
+ *
+ * It provides a basic STL allocator interface. It allows to use a stateful
+ * allocator:
+ *
+ * std::vector>
+ *
+ * @see ScopedLinearAllocator
+ */
+template
+class AllocatorAdapter
+{
+public:
+ using value_type = T;
+
+ // We don't need runtime checks and per-element copies for containers
+ // when we have a unique allocator (means no mixing different allocators).
+ using propagate_on_container_copy_assignment = std::true_type;
+ using propagate_on_container_move_assignment = std::true_type;
+ using propagate_on_container_swap = std::true_type;
+ using is_always_equal = std::false_type;
+
+ template
+ struct rebind
+ {
+ using other = AllocatorAdapter;
+ };
+
+ constexpr AllocatorAdapter(Allocator& allocator) noexcept : m_Allocator(std::addressof(allocator)) {}
+ template
+ constexpr AllocatorAdapter(const AllocatorAdapter& allocatorAdapter) noexcept : m_Allocator(allocatorAdapter.m_Allocator) {}
+
+ [[nodiscard]] constexpr T* allocate(std::size_t n)
+ {
+ return static_cast(m_Allocator->allocate(n * sizeof(T), alignof(T)));
+ }
+
+ constexpr void deallocate(T* ptr, std::size_t n)
+ {
+ m_Allocator->deallocate(static_cast(ptr), n * sizeof(T));
+ }
+
+ template
+ constexpr bool operator==(const AllocatorAdapter& other) const noexcept
+ {
+ return m_Allocator == other.m_Allocator;
+ }
+
+private:
+ template friend class AllocatorAdapter;
+
+ // A pointer to make it moveable.
+ Allocator* m_Allocator{nullptr};
+};
+
+} // namespace PS::Memory
+
+#endif // INCLUDED_PS_ALLOCATORADAPTER
diff --git a/source/ps/memory/tests/test_AllocatorAdapter.h b/source/ps/memory/tests/test_AllocatorAdapter.h
new file mode 100644
index 0000000000..ac5407c266
--- /dev/null
+++ b/source/ps/memory/tests/test_AllocatorAdapter.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2026 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#include "lib/self_test.h"
+
+#include "ps/memory/AllocatorAdapter.h"
+#include "ps/memory/LinearAllocator.h"
+
+#include
+#include
+#include