diff --git a/src/qt/block_db.cpp b/src/qt/block_db.cpp
new file mode 100644
index 0000000..4d63320
--- /dev/null
+++ b/src/qt/block_db.cpp
@@ -0,0 +1,82 @@
+/* Copyright 2020-2025, Michele Santullo
+ * This file is part of memoserv.
+ *
+ * Memoserv 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Memoserv 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 Memoserv. If not, see .
+ */
+
+#include "block_db.hpp"
+#include "memcard/block.hpp"
+#include
+#include
+#include
+#include
+#include
+
+namespace duck {
+BlockEntry::BlockEntry (
+ std::vector>&& blocks,
+ std::unique_ptr&& data
+) :
+ blocks(std::move(blocks)),
+ data(std::move(data))
+{
+}
+
+std::string_view BlockEntry::identifier() const {
+ return blocks.front().identifier();
+}
+
+std::string BlockEntry::title() const {
+ return blocks.front().title();
+}
+
+BlockDb::BlockDb() = default;
+BlockDb::~BlockDb() noexcept = default;
+
+template
+unsigned int BlockDb::add (const std::vector >& blocks) {
+ constexpr auto block_size = mc::psx::BasicBlock::BlockByteSize;
+ constexpr auto frame_size = mc::psx::BasicFrame::Size;
+ assert(not blocks.empty());
+
+ const auto block_count = blocks.size();
+ assert(block_count == blocks.front().block_count());
+ auto mem = std::make_unique(frame_size + block_size * block_count);
+
+ uint8_t* dest = std::copy(blocks.front().toc().cbegin(), blocks.front().toc().cend(), mem.get());
+ assert(std::distance(mem.get(), dest) == frame_size);
+ mc::psx::Frame toc(mem.get(), 0);
+
+ std::vector> new_blocks;
+ new_blocks.reserve(block_count);
+ std::size_t index = 0;
+ for (const auto& block : blocks) {
+ assert(block_count == block.block_count() or block.block_count() == 0);
+ uint8_t* const curr_block_ptr = dest;
+ dest = std::copy(block.data(), block.data() + block_size, dest);
+ new_blocks.emplace_back(curr_block_ptr, toc, index++);
+ }
+
+ const std::size_t new_id = m_next_id++;
+ m_saves.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(new_id),
+ std::forward_as_tuple(std::move(new_blocks), std::move(mem))
+ );
+ return static_cast(m_saves.size() - 1u);
+}
+
+template unsigned int BlockDb::add (const std::vector >&);
+template unsigned int BlockDb::add (const std::vector >&);
+} //namespace duck
diff --git a/src/qt/block_db.hpp b/src/qt/block_db.hpp
new file mode 100644
index 0000000..a8d8f50
--- /dev/null
+++ b/src/qt/block_db.hpp
@@ -0,0 +1,57 @@
+/* Copyright 2020-2025, Michele Santullo
+ * This file is part of memoserv.
+ *
+ * Memoserv 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Memoserv 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 Memoserv. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include