diff --git a/Makefile b/Makefile
index 26e4c3b3..ea746be8 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
# INCLUDES is a list of directories containing header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
-SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr
+SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb
DATA := data
INCLUDES := include
diff --git a/include/stratosphere/kvdb/kvdb_archive.hpp b/include/stratosphere/kvdb/kvdb_archive.hpp
new file mode 100644
index 00000000..64fe074d
--- /dev/null
+++ b/include/stratosphere/kvdb/kvdb_archive.hpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program. If not, see .
+ */
+
+#pragma once
+#include
+#include "../defines.hpp"
+#include "../results.hpp"
+
+#include "kvdb_auto_buffer.hpp"
+
+namespace sts::kvdb {
+
+ /* Functionality for parsing/generating a key value archive. */
+ class ArchiveReader {
+ private:
+ AutoBuffer &buffer;
+ size_t offset;
+ public:
+ ArchiveReader(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
+ private:
+ Result Peek(void *dst, size_t size);
+ Result Read(void *dst, size_t size);
+ public:
+ Result ReadEntryCount(size_t *out);
+ Result GetEntrySize(size_t *out_key_size, size_t *out_value_size);
+ Result ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size);
+ };
+
+ class ArchiveWriter {
+ private:
+ AutoBuffer &buffer;
+ size_t offset;
+ public:
+ ArchiveWriter(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
+ private:
+ Result Write(const void *src, size_t size);
+ public:
+ void WriteHeader(size_t entry_count);
+ void WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size);
+ };
+
+ class ArchiveSizeHelper {
+ private:
+ size_t size;
+ public:
+ ArchiveSizeHelper();
+
+ void AddEntry(size_t key_size, size_t value_size);
+
+ size_t GetSize() const {
+ return this->size;
+ }
+ };
+
+}
\ No newline at end of file
diff --git a/include/stratosphere/kvdb/kvdb_auto_buffer.hpp b/include/stratosphere/kvdb/kvdb_auto_buffer.hpp
new file mode 100644
index 00000000..6fe115ad
--- /dev/null
+++ b/include/stratosphere/kvdb/kvdb_auto_buffer.hpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program. If not, see .
+ */
+
+#pragma once
+#include
+#include "../defines.hpp"
+#include "../results.hpp"
+
+namespace sts::kvdb {
+
+ class AutoBuffer {
+ NON_COPYABLE(AutoBuffer);
+ private:
+ std::unique_ptr buffer;
+ size_t size;
+ public:
+ AutoBuffer() : size(0) { /* ... */ }
+
+ AutoBuffer(AutoBuffer &&rhs) {
+ this->buffer = std::move(rhs.buffer);
+ this->size = rhs.size;
+ rhs.size = 0;
+ }
+
+ AutoBuffer& operator=(AutoBuffer &&rhs) {
+ rhs.Swap(*this);
+ return *this;
+ }
+
+ void Swap(AutoBuffer &rhs) {
+ std::swap(this->buffer, rhs.buffer);
+ std::swap(this->size, rhs.size);
+ }
+
+ void Reset() {
+ this->buffer.reset();
+ this->size = 0;
+ }
+
+ u8 *Get() const {
+ return this->buffer.get();
+ }
+
+ size_t GetSize() const {
+ return this->size;
+ }
+
+ Result Initialize(size_t size) {
+ /* Check that we're not already initialized. */
+ if (this->buffer != nullptr) {
+ std::abort();
+ }
+
+ /* Allocate a buffer. */
+ auto mem = new (std::nothrow) u8[size];
+ if (mem == nullptr) {
+ return ResultKvdbAllocationFailed;
+ }
+
+ this->buffer.reset(mem);
+ this->size = size;
+ return ResultSuccess;
+ }
+
+ Result Initialize(const void *buf, size_t size) {
+ /* Create a new buffer of the right size. */
+ R_TRY(this->Initialize(size));
+
+ /* Copy the input data in. */
+ std::memcpy(this->buffer.get(), buf, size);
+
+ return ResultSuccess;
+ }
+ };
+}
\ No newline at end of file
diff --git a/include/stratosphere/kvdb/kvdb_bounded_string.hpp b/include/stratosphere/kvdb/kvdb_bounded_string.hpp
new file mode 100644
index 00000000..16c02ef0
--- /dev/null
+++ b/include/stratosphere/kvdb/kvdb_bounded_string.hpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program. If not, see .
+ */
+
+#pragma once
+#include
+#include
+#include
+#include "../defines.hpp"
+#include "../results.hpp"
+
+namespace sts::kvdb {
+
+ /* Represents a string with a backing buffer of N bytes. */
+ template
+ class BoundedString {
+ static_assert(N > 0, "BoundedString requires non-zero backing buffer!");
+ private:
+ char buffer[N];
+ private:
+ /* Utility. */
+ static inline void CheckLength(size_t len) {
+ if (len >= N) {
+ std::abort();
+ }
+ }
+ public:
+ /* Constructors. */
+ BoundedString() {
+ buffer[0] = 0;
+ }
+
+ explicit BoundedString(const char *s) {
+ this->Set(s);
+ }
+
+ /* Static constructors. */
+ static BoundedString Make(const char *s) {
+ return BoundedString(s);
+ }
+
+ static BoundedString MakeFormat(const char *format, ...) __attribute__((format (printf, 1, 2))) {
+ BoundedString string;
+
+ std::va_list args;
+ va_start(args, format);
+ CheckLength(std::vsnprintf(string.buffer, N, format, args));
+ va_end(args);
+
+ return string;
+ }
+
+ /* Getters. */
+ size_t GetLength() const {
+ return strnlen(this->buffer, N);
+ }
+
+ const char *Get() const {
+ return this->buffer;
+ }
+
+ operator const char *() const {
+ return this->buffer;
+ }
+
+ /* Setters. */
+ void Set(const char *s) {
+ /* Ensure string can fit in our buffer. */
+ CheckLength(strnlen(s, N));
+ std::strncpy(this->buffer, s, N);
+ }
+
+ void SetFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
+ /* Format into the buffer, abort if too large. */
+ std::va_list args;
+ va_start(args, format);
+ CheckLength(std::vsnprintf(this->buffer, N, format, args));
+ va_end(args);
+ }
+
+ /* Append to existing. */
+ void Append(const char *s) {
+ const size_t length = GetLength();
+ CheckLength(length + strnlen(s, N));
+ std::strncat(this->buffer, s, N - length - 1);
+ }
+
+ void Append(char c) {
+ const size_t length = GetLength();
+ CheckLength(length + 1);
+ this->buffer[length] = c;
+ this->buffer[length + 1] = 0;
+ }
+
+ void AppendFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
+ const size_t length = GetLength();
+ std::va_list args;
+ va_start(args, format);
+ CheckLength(std::vsnprintf(this->buffer + length, N - length, format, args) + length);
+ va_end(args);
+ }
+
+ /* Substring utilities. */
+ void GetSubstring(char *dst, size_t dst_size, size_t offset, size_t length) const {
+ /* Make sure output buffer can hold the substring. */
+ if (offset + length > GetLength() || dst_size <= length) {
+ std::abort();
+ }
+ /* Copy substring to dst. */
+ std::strncpy(dst, this->buffer + offset, length);
+ dst[length] = 0;
+ }
+
+ BoundedString GetSubstring(size_t offset, size_t length) const {
+ BoundedString string;
+ GetSubstring(string.buffer, N, offset, length);
+ return string;
+ }
+
+ /* Comparison. */
+ constexpr bool operator==(const BoundedString &rhs) const {
+ return std::strncmp(this->buffer, rhs.buffer, N) == 0;
+ }
+
+ constexpr bool operator!=(const BoundedString &rhs) const {
+ return !(*this == rhs);
+ }
+
+ bool EndsWith(const char *s, size_t offset) const {
+ return std::strncmp(this->buffer + offset, s, N - offset) == 0;
+ }
+
+ bool EndsWith(const char *s) {
+ const size_t suffix_length = strnlen(s, N);
+ const size_t length = GetLength();
+ return suffix_length <= length && EndsWith(s, length - suffix_length);
+ }
+ };
+
+}
\ No newline at end of file
diff --git a/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp b/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp
new file mode 100644
index 00000000..984fe5f8
--- /dev/null
+++ b/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program. If not, see .
+ */
+
+#pragma once
+#include
+#include
+#include
+#include "kvdb_auto_buffer.hpp"
+#include "kvdb_archive.hpp"
+#include "kvdb_bounded_string.hpp"
+#include "kvdb_memory_key_value_store.hpp"
+
+namespace sts::kvdb {
+
+ template
+ class MemoryKeyValueStore {
+ static_assert(std::is_pod::value, "KeyValueStore Keys must be pod!");
+ NON_COPYABLE(MemoryKeyValueStore);
+ NON_MOVEABLE(MemoryKeyValueStore);
+ public:
+ /* Subtypes. */
+ class Entry {
+ private:
+ Key key;
+ void *value;
+ size_t value_size;
+ public:
+ constexpr Entry(const Key &k, void *v, size_t s) : key(k), value(v), value_size(s) { /* ... */ }
+
+ const Key &GetKey() const {
+ return this->key;
+ }
+
+ template
+ Value *GetValuePointer() {
+ /* Size check. Note: Nintendo does not size check. */
+ if constexpr (!std::is_same::value) {
+ if (sizeof(Value) > this->value_size) {
+ std::abort();
+ }
+ /* Ensure we only get pod. */
+ static_assert(std::is_pod::value, "KeyValueStore Values must be pod");
+ }
+ return reinterpret_cast(this->value);
+ }
+
+ template
+ const Value *GetValuePointer() const {
+ /* Size check. Note: Nintendo does not size check. */
+ if constexpr (!std::is_same::value) {
+ if (sizeof(Value) > this->value_size) {
+ std::abort();
+ }
+ /* Ensure we only get pod. */
+ static_assert(std::is_pod::value, "KeyValueStore Values must be pod");
+ }
+ return reinterpret_cast(this->value);
+ }
+
+ template
+ Value &GetValue() {
+ return *(this->GetValuePointer());
+ }
+
+ template
+ const Value &GetValue() const {
+ return *(this->GetValuePointer());
+ }
+
+ size_t GetValueSize() const {
+ return this->value_size;
+ }
+
+ constexpr inline bool operator<(const Key &rhs) const {
+ return key < rhs;
+ }
+
+ constexpr inline bool operator==(const Key &rhs) const {
+ return key == rhs;
+ }
+ };
+
+ class Index {
+ private:
+ size_t count;
+ size_t capacity;
+ Entry *entries;
+ public:
+ Index() : count(0), capacity(0), entries(nullptr) { /* ... */ }
+
+ ~Index() {
+ if (this->entries != nullptr) {
+ this->ResetEntries();
+ std::free(this->entries);
+ this->entries = nullptr;
+ }
+ }
+
+ size_t GetCount() const {
+ return this->count;
+ }
+
+ size_t GetCapacity() const {
+ return this->capacity;
+ }
+
+ void ResetEntries() {
+ for (size_t i = 0; i < this->count; i++) {
+ std::free(this->entries[i].GetValuePointer());
+ }
+ this->count = 0;
+ }
+
+ Result Initialize(size_t capacity) {
+ this->entries = reinterpret_cast(std::malloc(sizeof(Entry) * capacity));
+ if (this->entries == nullptr) {
+ return ResultKvdbAllocationFailed;
+ }
+ this->capacity = capacity;
+ return ResultSuccess;
+ }
+
+ Result Set(const Key &key, const void *value, size_t value_size) {
+ /* Allocate new value. */
+ void *new_value = std::malloc(value_size);
+ if (new_value == nullptr) {
+ return ResultKvdbAllocationFailed;
+ }
+ std::memcpy(new_value, value, value_size);
+
+ /* Find entry for key. */
+ Entry *it = this->lower_bound(key);
+ if (it != this->end() && it->GetKey() == key) {
+ /* Entry already exists. Free old value. */
+ std::free(it->GetValuePointer());
+ } else {
+ /* We need to add a new entry. Check we have room, move future keys forward. */
+ if (this->count >= this->capacity) {
+ std::free(new_value);
+ return ResultKvdbTooManyKeys;
+ }
+ std::memmove(it + 1, it, sizeof(*it) * (this->end() - it));
+ this->count++;
+ }
+
+ /* Save the new Entry in the map. */
+ *it = Entry(key, new_value, value_size);
+ return ResultSuccess;
+ }
+
+ Result AddUnsafe(const Key &key, void *value, size_t value_size) {
+ if (this->count >= this->capacity) {
+ return ResultKvdbTooManyKeys;
+ }
+
+ this->entries[this->count++] = Entry(key, value, value_size);
+ return ResultSuccess;
+ }
+
+ Result Remove(const Key &key) {
+ /* Find entry for key. */
+ Entry *it = this->find(key);
+
+ /* Check if the entry is valid. */
+ if (it != this->end()) {
+ /* Free the value, move entries back. */
+ std::free(it->GetValuePointer());
+ std::memmove(it, it + 1, sizeof(*it) * (this->end() - (it + 1)));
+ this->count--;
+ return ResultSuccess;
+ }
+
+ /* If it's not, we didn't remove it. */
+ return ResultKvdbKeyNotFound;
+ }
+
+ Entry *begin() {
+ return this->GetBegin();
+ }
+
+ const Entry *begin() const {
+ return this->GetBegin();
+ }
+
+ Entry *end() {
+ return this->GetEnd();
+ }
+
+ const Entry *end() const {
+ return this->GetEnd();
+ }
+
+ const Entry *cbegin() const {
+ return this->begin();
+ }
+
+ const Entry *cend() const {
+ return this->end();
+ }
+
+ Entry *lower_bound(const Key &key) {
+ return this->GetLowerBound(key);
+ }
+
+ const Entry *lower_bound(const Key &key) const {
+ return this->GetLowerBound(key);
+ }
+
+ Entry *find(const Key &key) {
+ return this->Find(key);
+ }
+
+ const Entry *find(const Key &key) const {
+ return this->Find(key);
+ }
+ private:
+ Entry *GetBegin() {
+ return this->entries;
+ }
+
+ const Entry *GetBegin() const {
+ return this->entries;
+ }
+
+ Entry *GetEnd() {
+ return this->GetBegin() + this->count;
+ }
+
+ const Entry *GetEnd() const {
+ return this->GetBegin() + this->count;
+ }
+
+ Entry *GetLowerBound(const Key &key) {
+ return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
+ }
+
+ const Entry *GetLowerBound(const Key &key) const {
+ return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
+ }
+
+ Entry *Find(const Key &key) {
+ auto it = this->GetLowerBound(key);
+ auto end = this->GetEnd();
+ if (it != end && it->GetKey() == key) {
+ return it;
+ }
+ return end;
+ }
+
+ const Entry *Find(const Key &key) const {
+ auto it = this->GetLowerBound(key);
+ auto end = this->GetEnd();
+ if (it != end && it->GetKey() == key) {
+ return it;
+ }
+ return end;
+ }
+ };
+ private:
+ static constexpr size_t MaxPathLen = 0x300; /* TODO: FS_MAX_PATH - 1? */
+ using Path = kvdb::BoundedString;
+ private:
+ Index index;
+ Path path;
+ Path temp_path;
+ public:
+ MemoryKeyValueStore() { /* ... */ }
+
+ Result Initialize(const char *dir, size_t capacity) {
+ /* Ensure that the passed path is a directory. */
+ {
+ struct stat st;
+ if (stat(dir, &st) != 0 || !(S_ISDIR(st.st_mode))) {
+ return ResultFsPathNotFound;
+ }
+ }
+
+ /* Set paths. */
+ this->path.SetFormat("%s%s", dir, "/imkvdb.arc");
+ this->temp_path.SetFormat("%s%s", dir, "/imkvdb.tmp");
+
+ /* Initialize our index. */
+ R_TRY(this->index.Initialize(capacity));
+ return ResultSuccess;
+ }
+
+ Result Initialize(size_t capacity) {
+ /* This initializes without an archive file. */
+ /* A store initialized this way cannot have its contents loaded from or flushed to disk. */
+ this->path.Set("");
+ this->temp_path.Set("");
+
+ /* Initialize our index. */
+ R_TRY(this->index.Initialize(capacity));
+ return ResultSuccess;
+ }
+
+ size_t GetCount() const {
+ return this->index.GetCount();
+ }
+
+ size_t GetCapacity() const {
+ return this->index.GetCapacity();
+ }
+
+ Result Load() {
+ /* Reset any existing entries. */
+ this->index.ResetEntries();
+
+ /* Try to read the archive -- note, path not found is a success condition. */
+ /* This is because no archive file = no entries, so we're in the right state. */
+ AutoBuffer buffer;
+ R_TRY_CATCH(this->ReadArchiveFile(&buffer)) {
+ R_CATCH(ResultFsPathNotFound) {
+ return ResultSuccess;
+ }
+ } R_END_TRY_CATCH;
+
+ /* Parse entries from the buffer. */
+ {
+ ArchiveReader reader(buffer);
+
+ size_t entry_count = 0;
+ R_TRY(reader.ReadEntryCount(&entry_count));
+
+ for (size_t i = 0; i < entry_count; i++) {
+ /* Get size of key/value. */
+ size_t key_size = 0, value_size = 0;
+ R_TRY(reader.GetEntrySize(&key_size, &value_size));
+
+ /* Allocate memory for value. */
+ void *new_value = std::malloc(value_size);
+ if (new_value == nullptr) {
+ return ResultKvdbAllocationFailed;
+ }
+ auto value_guard = SCOPE_GUARD { std::free(new_value); };
+
+ /* Read key and value. */
+ Key key;
+ R_TRY(reader.ReadEntry(&key, sizeof(key), new_value, value_size));
+ R_TRY(this->index.AddUnsafe(key, new_value, value_size));
+
+ /* We succeeded, so cancel the value guard to prevent deallocation. */
+ value_guard.Cancel();
+ }
+ }
+
+ return ResultSuccess;
+ }
+
+ Result Save() {
+ /* Create a buffer to hold the archive. */
+ AutoBuffer buffer;
+ R_TRY(buffer.Initialize(this->GetArchiveSize()));
+
+ /* Write the archive to the buffer. */
+ {
+ ArchiveWriter writer(buffer);
+ writer.WriteHeader(this->GetCount());
+ for (const auto &it : this->index) {
+ const auto &key = it.GetKey();
+ writer.WriteEntry(&key, sizeof(Key), it.GetValuePointer(), it.GetValueSize());
+ }
+ }
+
+ /* Save the buffer to disk. */
+ return this->Commit(buffer);
+ }
+
+ Result Set(const Key &key, const void *value, size_t value_size) {
+ return this->index.Set(key, value, value_size);
+ }
+
+ template
+ Result Set(const Key &key, const Value &value) {
+ /* Only allow setting pod. */
+ static_assert(std::is_pod::value, "KeyValueStore Values must be pod");
+ return this->Set(key, &value, sizeof(Value));
+ }
+
+ template
+ Result Set(const Key &key, const Value *value) {
+ /* Only allow setting pod. */
+ static_assert(std::is_pod::value, "KeyValueStore Values must be pod");
+ return this->Set(key, value, sizeof(Value));
+ }
+
+ Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) {
+ /* Find entry. */
+ auto it = this->find(key);
+ if (it == this->end()) {
+ return ResultKvdbKeyNotFound;
+ }
+
+ size_t size = std::min(max_out_size, it.GetValueSize());
+ std::memcpy(out_value, it.GetValuePointer(), size);
+ *out_size = size;
+ return ResultSuccess;
+ }
+
+ template
+ Result GetValuePointer(Value **out_value, const Key &key) {
+ /* Find entry. */
+ auto it = this->find(key);
+ if (it == this->end()) {
+ return ResultKvdbKeyNotFound;
+ }
+
+ *out_value = it->template GetValuePointer();
+ return ResultSuccess;
+ }
+
+ template
+ Result GetValuePointer(const Value **out_value, const Key &key) const {
+ /* Find entry. */
+ auto it = this->find(key);
+ if (it == this->end()) {
+ return ResultKvdbKeyNotFound;
+ }
+
+ *out_value = it->template GetValuePointer();
+ return ResultSuccess;
+ }
+
+ template
+ Result GetValue(Value *out_value, const Key &key) const {
+ /* Find entry. */
+ auto it = this->find(key);
+ if (it == this->end()) {
+ return ResultKvdbKeyNotFound;
+ }
+
+ *out_value = it->template GetValue();
+ return ResultSuccess;
+ }
+
+ Result GetValueSize(size_t *out_size, const Key &key) const {
+ /* Find entry. */
+ auto it = this->find(key);
+ if (it == this->end()) {
+ return ResultKvdbKeyNotFound;
+ }
+
+ *out_size = it->GetValueSize();
+ return ResultSuccess;
+ }
+
+ Result Remove(const Key &key) {
+ return this->index.Remove(key);
+ }
+
+ Entry *begin() {
+ return this->index.begin();
+ }
+
+ const Entry *begin() const {
+ return this->index.begin();
+ }
+
+ Entry *end() {
+ return this->index.end();
+ }
+
+ const Entry *end() const {
+ return this->index.end();
+ }
+
+ const Entry *cbegin() const {
+ return this->index.cbegin();
+ }
+
+ const Entry *cend() const {
+ return this->index.cend();
+ }
+
+ Entry *lower_bound(const Key &key) {
+ return this->index.lower_bound(key);
+ }
+
+ const Entry *lower_bound(const Key &key) const {
+ return this->index.lower_bound(key);
+ }
+
+ Entry *find(const Key &key) {
+ return this->index.find(key);
+ }
+
+ const Entry *find(const Key &key) const {
+ return this->index.find(key);
+ }
+ private:
+ Result Commit(const AutoBuffer &buffer) {
+ /* Try to delete temporary archive, but allow deletion failure (it may not exist). */
+ std::remove(this->temp_path.Get());
+
+ /* Write data to the temporary archive. */
+ {
+ FILE *f = fopen(this->temp_path, "wb");
+ if (f == nullptr) {
+ return fsdevGetLastResult();
+ }
+ ON_SCOPE_EXIT { fclose(f); };
+
+ if (fwrite(buffer.Get(), buffer.GetSize(), 1, f) != 1) {
+ return fsdevGetLastResult();
+ }
+ }
+
+ /* Try to delete the saved archive, but allow deletion failure. */
+ std::remove(this->path.Get());
+
+ /* Rename the path. */
+ if (std::rename(this->temp_path.Get(), this->path.Get()) != 0) {
+ return fsdevGetLastResult();
+ }
+
+ return ResultSuccess;
+ }
+
+ size_t GetArchiveSize() const {
+ ArchiveSizeHelper size_helper;
+
+ for (const auto &it : this->index) {
+ size_helper.AddEntry(sizeof(Key), it.GetValueSize());
+ }
+
+ return size_helper.GetSize();
+ }
+
+ Result ReadArchiveFile(AutoBuffer *dst) const {
+ /* Open the file. */
+ FILE *f = fopen(this->path, "rb");
+ if (f == nullptr) {
+ return fsdevGetLastResult();
+ }
+ ON_SCOPE_EXIT { fclose(f); };
+
+ /* Get the archive file size. */
+ fseek(f, 0, SEEK_END);
+ const size_t archive_size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ /* Make a new buffer, read the file. */
+ R_TRY(dst->Initialize(archive_size));
+ if (fread(dst->Get(), archive_size, 1, f) != 1) {
+ return fsdevGetLastResult();
+ }
+
+ return ResultSuccess;
+ }
+ };
+
+}
\ No newline at end of file
diff --git a/include/stratosphere/results/kvdb_results.hpp b/include/stratosphere/results/kvdb_results.hpp
index 8248f8f2..97351914 100644
--- a/include/stratosphere/results/kvdb_results.hpp
+++ b/include/stratosphere/results/kvdb_results.hpp
@@ -19,7 +19,7 @@
static constexpr u32 Module_Kvdb = 20;
-static constexpr Result ResultKvdbTooLargeKey = MAKERESULT(Module_Kvdb, 1);
+static constexpr Result ResultKvdbTooManyKeys = MAKERESULT(Module_Kvdb, 1);
static constexpr Result ResultKvdbKeyNotFound = MAKERESULT(Module_Kvdb, 2);
static constexpr Result ResultKvdbAllocationFailed = MAKERESULT(Module_Kvdb, 4);
static constexpr Result ResultKvdbInvalidKeyValue = MAKERESULT(Module_Kvdb, 5);
diff --git a/source/kvdb/kvdb_archive.cpp b/source/kvdb/kvdb_archive.cpp
new file mode 100644
index 00000000..503f155f
--- /dev/null
+++ b/source/kvdb/kvdb_archive.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program. If not, see .
+ */
+
+#include
+#include
+
+namespace sts::kvdb {
+
+ namespace {
+
+ /* Convenience definitions. */
+ constexpr u8 ArchiveHeaderMagic[4] = {'I', 'M', 'K', 'V'};
+ constexpr u8 ArchiveEntryMagic[4] = {'I', 'M', 'E', 'N'};
+
+ /* Archive types. */
+ struct ArchiveHeader {
+ u8 magic[sizeof(ArchiveHeaderMagic)];
+ u32 pad;
+ u32 entry_count;
+
+ Result Validate() const {
+ if (std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) != 0) {
+ return ResultKvdbInvalidKeyValue;
+ }
+ return ResultSuccess;
+ }
+
+ static ArchiveHeader Make(size_t entry_count) {
+ ArchiveHeader header = {};
+ std::memcpy(header.magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic));
+ header.entry_count = static_cast(entry_count);
+ return header;
+ }
+ };
+ static_assert(sizeof(ArchiveHeader) == 0xC && std::is_pod::value, "ArchiveHeader definition!");
+
+ struct ArchiveEntryHeader {
+ u8 magic[sizeof(ArchiveEntryMagic)];
+ u32 key_size;
+ u32 value_size;
+
+ Result Validate() const {
+ if (std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) != 0) {
+ return ResultKvdbInvalidKeyValue;
+ }
+ return ResultSuccess;
+ }
+
+ static ArchiveEntryHeader Make(size_t ksz, size_t vsz) {
+ ArchiveEntryHeader header = {};
+ std::memcpy(header.magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic));
+ header.key_size = ksz;
+ header.value_size = vsz;
+ return header;
+ }
+ };
+ static_assert(sizeof(ArchiveEntryHeader) == 0xC && std::is_pod::value, "ArchiveEntryHeader definition!");
+
+ }
+
+ /* Reader functionality. */
+ Result ArchiveReader::Peek(void *dst, size_t size) {
+ /* Bounds check. */
+ if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
+ return ResultKvdbInvalidKeyValue;
+ }
+
+ std::memcpy(dst, this->buffer.Get() + this->offset, size);
+ return ResultSuccess;
+ }
+
+ Result ArchiveReader::Read(void *dst, size_t size) {
+ R_TRY(this->Peek(dst, size));
+ this->offset += size;
+ return ResultSuccess;
+ }
+
+ Result ArchiveReader::ReadEntryCount(size_t *out) {
+ /* This should only be called at the start of reading stream. */
+ if (this->offset != 0) {
+ std::abort();
+ }
+
+ /* Read and validate header. */
+ ArchiveHeader header;
+ R_TRY(this->Read(&header, sizeof(header)));
+ R_TRY(header.Validate());
+
+ *out = header.entry_count;
+ return ResultSuccess;
+ }
+
+ Result ArchiveReader::GetEntrySize(size_t *out_key_size, size_t *out_value_size) {
+ /* This should only be called after ReadEntryCount. */
+ if (this->offset == 0) {
+ std::abort();
+ }
+
+ /* Peek the next entry header. */
+ ArchiveEntryHeader header;
+ R_TRY(this->Peek(&header, sizeof(header)));
+ R_TRY(header.Validate());
+
+ *out_key_size = header.key_size;
+ *out_value_size = header.value_size;
+ return ResultSuccess;
+ }
+
+ Result ArchiveReader::ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size) {
+ /* This should only be called after ReadEntryCount. */
+ if (this->offset == 0) {
+ std::abort();
+ }
+
+ /* Read the next entry header. */
+ ArchiveEntryHeader header;
+ R_TRY(this->Read(&header, sizeof(header)));
+ R_TRY(header.Validate());
+
+ /* Key size and Value size must be correct. */
+ if (key_size != header.key_size || value_size != header.value_size) {
+ std::abort();
+ }
+
+ R_ASSERT(this->Read(out_key, key_size));
+ R_ASSERT(this->Read(out_value, value_size));
+ return ResultSuccess;
+ }
+
+ /* Writer functionality. */
+ Result ArchiveWriter::Write(const void *src, size_t size) {
+ /* Bounds check. */
+ if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
+ return ResultKvdbInvalidKeyValue;
+ }
+
+ std::memcpy(this->buffer.Get() + this->offset, src, size);
+ this->offset += size;
+ return ResultSuccess;
+ }
+
+ void ArchiveWriter::WriteHeader(size_t entry_count) {
+ /* This should only be called at start of write. */
+ if (this->offset != 0) {
+ std::abort();
+ }
+
+ ArchiveHeader header = ArchiveHeader::Make(entry_count);
+ R_ASSERT(this->Write(&header, sizeof(header)));
+ }
+
+ void ArchiveWriter::WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size) {
+ /* This should only be called after writing header. */
+ if (this->offset == 0) {
+ std::abort();
+ }
+
+ ArchiveEntryHeader header = ArchiveEntryHeader::Make(key_size, value_size);
+ R_ASSERT(this->Write(&header, sizeof(header)));
+ R_ASSERT(this->Write(key, key_size));
+ R_ASSERT(this->Write(value, value_size));
+ }
+
+ /* Size helper functionality. */
+ ArchiveSizeHelper::ArchiveSizeHelper() : size(sizeof(ArchiveHeader)) {
+ /* ... */
+ }
+
+ void ArchiveSizeHelper::AddEntry(size_t key_size, size_t value_size) {
+ this->size += sizeof(ArchiveEntryHeader) + key_size + value_size;
+ }
+
+}
\ No newline at end of file