summaryrefslogtreecommitdiffstats
path: root/src/core/io
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-12-11 15:14:26 +0500
committeruntodesu <kirill@untode.su>2025-12-11 15:14:26 +0500
commitf40d09cb8f712e87691af4912f3630d92d692779 (patch)
tree7ac3a4168ff722689372fd489c6f94d0a2546e8f /src/core/io
parent8bcbd2729388edc63c82d77d314b583af1447c49 (diff)
downloadvoxelius-f40d09cb8f712e87691af4912f3630d92d692779.tar.bz2
voxelius-f40d09cb8f712e87691af4912f3630d92d692779.zip
Shuffle stuff around
- Use the new and improved hierarchy I figured out when making Prospero chat - Re-add NSIS scripts, again from Prospero - Update most build and utility scripts with their most recent versions
Diffstat (limited to 'src/core/io')
-rw-r--r--src/core/io/CMakeLists.txt9
-rw-r--r--src/core/io/buffer.cc345
-rw-r--r--src/core/io/buffer.hh88
-rw-r--r--src/core/io/cmdline.cc95
-rw-r--r--src/core/io/cmdline.hh11
-rw-r--r--src/core/io/config_map.cc136
-rw-r--r--src/core/io/config_map.hh29
-rw-r--r--src/core/io/physfs.cc76
-rw-r--r--src/core/io/physfs.hh14
9 files changed, 803 insertions, 0 deletions
diff --git a/src/core/io/CMakeLists.txt b/src/core/io/CMakeLists.txt
new file mode 100644
index 0000000..3fe9fb1
--- /dev/null
+++ b/src/core/io/CMakeLists.txt
@@ -0,0 +1,9 @@
+target_sources(core PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/buffer.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/buffer.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/cmdline.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/cmdline.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/config_map.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/config_map.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/physfs.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/physfs.hh")
diff --git a/src/core/io/buffer.cc b/src/core/io/buffer.cc
new file mode 100644
index 0000000..0f781a0
--- /dev/null
+++ b/src/core/io/buffer.cc
@@ -0,0 +1,345 @@
+#include "core/pch.hh"
+
+#include "core/io/buffer.hh"
+
+#include "core/math/constexpr.hh"
+
+io::ReadBuffer::ReadBuffer(const ReadBuffer& other)
+{
+ reset(other.data(), other.size());
+}
+
+io::ReadBuffer::ReadBuffer(const void* data, std::size_t size)
+{
+ assert(data);
+
+ reset(data, size);
+}
+
+io::ReadBuffer::ReadBuffer(const ENetPacket* packet)
+{
+ assert(packet);
+
+ reset(packet);
+}
+
+io::ReadBuffer::ReadBuffer(PHYSFS_File* file)
+{
+ assert(file);
+
+ reset(file);
+}
+
+std::size_t io::ReadBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* io::ReadBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void io::ReadBuffer::reset(const void* data, std::size_t size)
+{
+ assert(data);
+
+ auto bytes = reinterpret_cast<const std::byte*>(data);
+ m_vector.assign(bytes, bytes + size);
+ m_position = 0U;
+}
+
+void io::ReadBuffer::reset(const ENetPacket* packet)
+{
+ assert(packet);
+
+ auto bytes_ptr = reinterpret_cast<const std::byte*>(packet->data);
+ m_vector.assign(bytes_ptr, bytes_ptr + packet->dataLength);
+ m_position = 0;
+}
+
+void io::ReadBuffer::reset(PHYSFS_File* file)
+{
+ assert(file);
+
+ m_vector.resize(PHYSFS_fileLength(file));
+ m_position = 0;
+
+ PHYSFS_seek(file, 0);
+ PHYSFS_readBytes(file, m_vector.data(), m_vector.size());
+}
+
+template<>
+std::byte io::ReadBuffer::read<std::byte>(void)
+{
+ if(m_position < m_vector.size()) {
+ auto result = m_vector[m_position];
+ m_position += 1U;
+ return result;
+ }
+
+ m_position += 1U;
+ return static_cast<std::byte>(0x00);
+}
+
+template<>
+std::uint8_t io::ReadBuffer::read<std::uint8_t>(void)
+{
+ if((m_position + 1U) <= m_vector.size()) {
+ auto result = static_cast<std::uint8_t>(m_vector[m_position]);
+ m_position += 1U;
+ return result;
+ }
+
+ m_position += 1U;
+ return 0;
+}
+
+template<>
+std::uint16_t io::ReadBuffer::read<std::uint16_t>(void)
+{
+ if((m_position + 2U) <= m_vector.size()) {
+ auto result = UINT16_C(0x0000);
+ result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 0U])) << 8U;
+ result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 1U])) << 0U;
+ m_position += 2U;
+ return result;
+ }
+
+ m_position += 2U;
+ return 0;
+}
+
+template<>
+std::uint32_t io::ReadBuffer::read<std::uint32_t>(void)
+{
+ if((m_position + 4U) <= m_vector.size()) {
+ auto result = UINT32_C(0x00000000);
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 0U])) << 24U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 1U])) << 16U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 2U])) << 8U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 3U])) << 0U;
+ m_position += 4U;
+ return result;
+ }
+
+ m_position += 4U;
+ return 0;
+}
+
+template<>
+std::uint64_t io::ReadBuffer::read<std::uint64_t>(void)
+{
+ if((m_position + 8U) <= m_vector.size()) {
+ auto result = UINT64_C(0x0000000000000000);
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 0U])) << 56U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 1U])) << 48U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 2U])) << 40U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 3U])) << 32U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 4U])) << 24U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 5U])) << 16U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 6U])) << 8U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 7U])) << 0U;
+ m_position += 8U;
+ return result;
+ }
+
+ m_position += 8U;
+ return 0;
+}
+
+template<>
+float io::ReadBuffer::read<float>(void)
+{
+ return std::bit_cast<float>(read<std::uint32_t>());
+}
+
+template<>
+std::int8_t io::ReadBuffer::read<std::int8_t>(void)
+{
+ return std::bit_cast<std::int8_t>(read<std::uint8_t>());
+}
+
+template<>
+std::int16_t io::ReadBuffer::read<std::int16_t>(void)
+{
+ return std::bit_cast<std::int16_t>(read<std::uint16_t>());
+}
+
+template<>
+std::int32_t io::ReadBuffer::read<std::int32_t>(void)
+{
+ return std::bit_cast<std::int32_t>(read<std::uint32_t>());
+}
+
+template<>
+std::int64_t io::ReadBuffer::read<std::int64_t>(void)
+{
+ return std::bit_cast<std::int64_t>(read<std::uint64_t>());
+}
+
+template<>
+std::string io::ReadBuffer::read<std::string>(void)
+{
+ std::string result;
+ result.resize(read<std::uint16_t>());
+
+ for(std::size_t i = 0; i < result.size(); ++i) {
+ if(m_position < m_vector.size()) {
+ result[i] = static_cast<char>(m_vector[m_position]);
+ }
+
+ m_position += 1U;
+ }
+
+ return result;
+}
+
+void io::ReadBuffer::read(void* buffer, std::size_t size)
+{
+ auto bytes = reinterpret_cast<std::byte*>(buffer);
+ auto amount_to_read = std::min(size, m_vector.size() - m_position);
+
+ if(amount_to_read > 0) {
+ std::copy(m_vector.cbegin() + m_position, m_vector.cbegin() + m_position + amount_to_read, bytes);
+ }
+
+ m_position += size;
+}
+
+io::WriteBuffer::WriteBuffer(const WriteBuffer& other)
+{
+ m_vector = other.m_vector;
+}
+
+std::size_t io::WriteBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* io::WriteBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void io::WriteBuffer::reset(void)
+{
+ m_vector.clear();
+}
+
+void io::WriteBuffer::write(const WriteBuffer& other)
+{
+ m_vector.insert(m_vector.end(), other.m_vector.begin(), other.m_vector.end());
+}
+
+void io::WriteBuffer::write(const void* data, std::size_t size)
+{
+ assert(data);
+
+ auto bytes = reinterpret_cast<const std::byte*>(data);
+ m_vector.insert(m_vector.end(), bytes, bytes + size);
+}
+
+template<>
+void io::WriteBuffer::write<std::byte>(const std::byte value)
+{
+ m_vector.push_back(value);
+}
+
+template<>
+void io::WriteBuffer::write<std::uint8_t>(const std::uint8_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(value));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint16_t>(const std::uint16_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0xFF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0x00FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint32_t>(const std::uint32_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0xFF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x00FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x0000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x000000FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint64_t>(const std::uint64_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0xFF00000000000000)) >> 56U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00FF000000000000)) >> 48U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000FF0000000000)) >> 40U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000FF00000000)) >> 32U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000FF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000000000FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000000000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000000000FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write(const float value)
+{
+ write(std::bit_cast<std::uint32_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int8_t value)
+{
+ write(std::bit_cast<std::uint8_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int16_t value)
+{
+ write(std::bit_cast<std::uint16_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int32_t value)
+{
+ write(std::bit_cast<std::uint32_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int64_t value)
+{
+ write(std::bit_cast<std::uint64_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write<std::string_view>(const std::string_view value)
+{
+ write<std::uint16_t>(static_cast<std::uint16_t>(value.size()));
+
+ for(const auto& character : value) {
+ m_vector.push_back(static_cast<std::byte>(character));
+ }
+}
+
+PHYSFS_File* io::WriteBuffer::to_file(const std::string& path, bool append) const
+{
+ PHYSFS_File* file = nullptr;
+
+ if(append) {
+ file = PHYSFS_openAppend(path.c_str());
+ }
+ else {
+ file = PHYSFS_openWrite(path.c_str());
+ }
+
+ if(file) {
+ PHYSFS_writeBytes(file, m_vector.data(), m_vector.size());
+ }
+
+ return file;
+}
+
+ENetPacket* io::WriteBuffer::to_packet(enet_uint32 flags) const
+{
+ return enet_packet_create(m_vector.data(), m_vector.size(), flags);
+}
diff --git a/src/core/io/buffer.hh b/src/core/io/buffer.hh
new file mode 100644
index 0000000..96a37b1
--- /dev/null
+++ b/src/core/io/buffer.hh
@@ -0,0 +1,88 @@
+namespace io
+{
+class ReadBuffer final {
+public:
+ ReadBuffer(void) = default;
+ explicit ReadBuffer(const ReadBuffer& other);
+ explicit ReadBuffer(const void* data, std::size_t size);
+ explicit ReadBuffer(const ENetPacket* packet);
+ explicit ReadBuffer(PHYSFS_File* file);
+ virtual ~ReadBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(const void* data, std::size_t size);
+ void reset(const ENetPacket* packet);
+ void reset(PHYSFS_File* file);
+
+ constexpr void rewind(void);
+ constexpr bool is_ended(void) const;
+
+ void read(void* buffer, std::size_t size);
+
+ template<typename T>
+ T read(void);
+
+ template<typename T>
+ ReadBuffer& operator>>(T& value);
+
+private:
+ std::vector<std::byte> m_vector;
+ std::size_t m_position;
+};
+} // namespace io
+
+namespace io
+{
+class WriteBuffer final {
+public:
+ WriteBuffer(void) = default;
+ explicit WriteBuffer(const WriteBuffer& other);
+ virtual ~WriteBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(void);
+
+ void write(const WriteBuffer& other);
+ void write(const void* data, std::size_t size);
+
+ template<typename T>
+ void write(const T value);
+
+ template<typename T>
+ WriteBuffer& operator<<(const T value);
+
+ PHYSFS_File* to_file(const std::string& path, bool append = false) const;
+ ENetPacket* to_packet(enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE) const;
+
+private:
+ std::vector<std::byte> m_vector;
+};
+} // namespace io
+
+constexpr void io::ReadBuffer::rewind(void)
+{
+ m_position = 0;
+}
+
+constexpr bool io::ReadBuffer::is_ended(void) const
+{
+ return m_position >= m_vector.size();
+}
+
+template<typename T>
+io::ReadBuffer& io::ReadBuffer::operator>>(T& value)
+{
+ value = read<T>();
+ return *this;
+}
+
+template<typename T>
+io::WriteBuffer& io::WriteBuffer::operator<<(const T value)
+{
+ write<T>(value);
+ return *this;
+}
diff --git a/src/core/io/cmdline.cc b/src/core/io/cmdline.cc
new file mode 100644
index 0000000..7a00f37
--- /dev/null
+++ b/src/core/io/cmdline.cc
@@ -0,0 +1,95 @@
+#include "core/pch.hh"
+
+#include "core/io/cmdline.hh"
+
+// Valid options always start with OPTION_PREFIX, can contain
+// a bunch of OPTION_PREFIX'es inside and never end with one
+constexpr static char OPTION_PREFIX = '-';
+
+static std::unordered_map<std::string, std::string> options;
+
+static inline bool is_option_string(const std::string& string)
+{
+ if(string.find_last_of(OPTION_PREFIX) >= (string.size() - 1)) {
+ return false;
+ }
+
+ return string[0] == OPTION_PREFIX;
+}
+
+static inline std::string get_option(const std::string& string)
+{
+ std::size_t i;
+ for(i = 0; string[i] == OPTION_PREFIX; ++i) {
+ // empty
+ }
+
+ return std::string(string.cbegin() + i, string.cend());
+}
+
+void io::cmdline::create(int argc, char** argv)
+{
+ for(int idx = 1; idx < argc; ++idx) {
+ std::string string = argv[idx];
+
+ if(!is_option_string(string)) {
+ spdlog::warn("cmdline: non-argument at {}: {}", idx, string);
+ continue;
+ }
+
+ auto option_string = get_option(string);
+ auto next_idx = idx + 1;
+
+ if(next_idx < argc) {
+ std::string argument = argv[next_idx];
+
+ if(!is_option_string(argument)) {
+ options.insert_or_assign(option_string, argument);
+ idx = next_idx;
+ continue;
+ }
+ }
+
+ // The option is either last or has no
+ // argument (happens when there is a valid
+ // option right next to the one we're parsing)
+ options.insert_or_assign(option_string, std::string());
+ }
+}
+
+void io::cmdline::insert(std::string_view option)
+{
+ options.insert_or_assign(std::string(option), std::string());
+}
+
+void io::cmdline::insert(std::string_view option, std::string_view argument)
+{
+ options.insert_or_assign(std::string(option), std::string(argument));
+}
+
+std::string_view io::cmdline::get(std::string_view option, std::string_view fallback)
+{
+ auto it = options.find(std::string(option));
+
+ if(it == options.cend()) {
+ return fallback;
+ }
+
+ return it->second;
+}
+
+const char* io::cmdline::get_cstr(std::string_view option, const char* fallback)
+{
+ auto it = options.find(std::string(option));
+
+ if(it == options.cend()) {
+ return fallback;
+ }
+
+ return it->second.c_str();
+}
+
+bool io::cmdline::contains(std::string_view option)
+{
+ return options.count(std::string(option));
+}
diff --git a/src/core/io/cmdline.hh b/src/core/io/cmdline.hh
new file mode 100644
index 0000000..e90433c
--- /dev/null
+++ b/src/core/io/cmdline.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace io::cmdline
+{
+void create(int argc, char** argv);
+void insert(std::string_view option);
+void insert(std::string_view option, std::string_view argument);
+std::string_view get(std::string_view option, std::string_view fallback = "");
+const char* get_cstr(std::string_view option, const char* fallback = nullptr);
+bool contains(std::string_view option);
+} // namespace io::cmdline
diff --git a/src/core/io/config_map.cc b/src/core/io/config_map.cc
new file mode 100644
index 0000000..57692a7
--- /dev/null
+++ b/src/core/io/config_map.cc
@@ -0,0 +1,136 @@
+#include "core/pch.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/config/ivalue.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/utils/string.hh"
+
+#include "core/version.hh"
+
+void io::ConfigMap::load_cmdline(void)
+{
+ for(auto it : m_values) {
+ if(auto value = io::cmdline::get_cstr(it.first.c_str())) {
+ it.second->set(value);
+ }
+ }
+}
+
+bool io::ConfigMap::load_file(std::string_view path)
+{
+ if(auto file = PHYSFS_openRead(std::string(path).c_str())) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::string line;
+ std::string kv_string;
+ std::istringstream stream(source);
+
+ while(std::getline(stream, line)) {
+ auto comment = line.find_first_of('#');
+
+ if(comment == std::string::npos) {
+ kv_string = utils::trim_whitespace(line);
+ }
+ else {
+ kv_string = utils::trim_whitespace(line.substr(0, comment));
+ }
+
+ if(utils::is_whitespace(kv_string)) {
+ // Ignore empty or commented out lines
+ continue;
+ }
+
+ auto separator = kv_string.find('=');
+
+ if(separator == std::string::npos) {
+ spdlog::warn("config: {}: invalid line: {}", path, line);
+ continue;
+ }
+
+ auto kv_name = utils::trim_whitespace(kv_string.substr(0, separator));
+ auto kv_value = utils::trim_whitespace(kv_string.substr(separator + 1));
+
+ auto kv_pair = m_values.find(kv_name);
+
+ if(kv_pair == m_values.cend()) {
+ spdlog::warn("config: {}: unknown key: {}", path, kv_name);
+ continue;
+ }
+
+ kv_pair->second->set(kv_value.c_str());
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool io::ConfigMap::save_file(std::string_view path) const
+{
+ std::ostringstream stream;
+
+ auto curtime = std::time(nullptr);
+
+ stream << "# Voxelius " << version::triplet << " configuration file" << std::endl;
+ stream << "# Generated at: " << std::put_time(std::gmtime(&curtime), "%Y-%m-%d %H:%M:%S %z") << std::endl << std::endl;
+
+ for(const auto& it : m_values) {
+ stream << it.first << "=";
+ stream << it.second->get();
+ stream << std::endl;
+ }
+
+ if(auto file = PHYSFS_openWrite(std::string(path).c_str())) {
+ auto source = stream.str();
+ PHYSFS_writeBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+ return true;
+ }
+
+ return false;
+}
+
+bool io::ConfigMap::set_value(std::string_view name, std::string_view value)
+{
+ auto kv_pair = m_values.find(std::string(name));
+
+ if(kv_pair != m_values.cend()) {
+ kv_pair->second->set(value);
+ return true;
+ }
+
+ return false;
+}
+
+std::string_view io::ConfigMap::get_value(std::string_view name) const
+{
+ auto kv_pair = m_values.find(std::string(name));
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second->get();
+ }
+
+ return std::string_view();
+}
+
+void io::ConfigMap::add_value(std::string_view name, config::IValue& vref)
+{
+ m_values.insert_or_assign(std::string(name), &vref);
+}
+
+const config::IValue* io::ConfigMap::find(std::string_view name) const
+{
+ auto kv_pair = m_values.find(std::string(name));
+
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second;
+ }
+ else {
+ return nullptr;
+ }
+}
diff --git a/src/core/io/config_map.hh b/src/core/io/config_map.hh
new file mode 100644
index 0000000..b0cd579
--- /dev/null
+++ b/src/core/io/config_map.hh
@@ -0,0 +1,29 @@
+#pragma once
+
+namespace config
+{
+class IValue;
+} // namespace config
+
+namespace io
+{
+class ConfigMap final {
+public:
+ ConfigMap(void) = default;
+ virtual ~ConfigMap(void) = default;
+
+ void load_cmdline(void);
+ bool load_file(std::string_view path);
+ bool save_file(std::string_view path) const;
+
+ bool set_value(std::string_view name, std::string_view value);
+ std::string_view get_value(std::string_view name) const;
+
+ void add_value(std::string_view name, config::IValue& vref);
+
+ const config::IValue* find(std::string_view name) const;
+
+private:
+ std::unordered_map<std::string, config::IValue*> m_values;
+};
+} // namespace io
diff --git a/src/core/io/physfs.cc b/src/core/io/physfs.cc
new file mode 100644
index 0000000..1c36f88
--- /dev/null
+++ b/src/core/io/physfs.cc
@@ -0,0 +1,76 @@
+#include "core/pch.hh"
+
+#include "core/io/physfs.hh"
+
+bool io::read_file(std::string_view path, std::vector<std::byte>& buffer)
+{
+ auto file = PHYSFS_openRead(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, physfs_error());
+ return false;
+ }
+
+ PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
+ buffer.resize(static_cast<std::size_t>(file_size));
+
+ PHYSFS_readBytes(file, buffer.data(), file_size);
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool io::read_file(std::string_view path, std::string& buffer)
+{
+ auto file = PHYSFS_openRead(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, physfs_error());
+ return false;
+ }
+
+ PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
+ buffer.resize(static_cast<std::size_t>(file_size));
+
+ PHYSFS_readBytes(file, buffer.data(), file_size);
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool io::write_file(std::string_view path, const std::vector<std::byte>& buffer)
+{
+ auto file = PHYSFS_openWrite(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, physfs_error());
+ return false;
+ }
+
+ PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size()));
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool io::write_file(std::string_view path, const std::string& buffer)
+{
+ auto file = PHYSFS_openWrite(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, physfs_error());
+ return false;
+ }
+
+ PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size()));
+ PHYSFS_close(file);
+
+ return true;
+}
+
+std::string_view io::physfs_error(void)
+{
+ auto error_code = PHYSFS_getLastErrorCode();
+ auto error_string = PHYSFS_getErrorByCode(error_code);
+ return std::string_view(error_string, std::strlen(error_string));
+}
diff --git a/src/core/io/physfs.hh b/src/core/io/physfs.hh
new file mode 100644
index 0000000..01282ad
--- /dev/null
+++ b/src/core/io/physfs.hh
@@ -0,0 +1,14 @@
+#pragma once
+
+namespace io
+{
+bool read_file(std::string_view path, std::vector<std::byte>& buffer);
+bool read_file(std::string_view path, std::string& buffer);
+bool write_file(std::string_view path, const std::vector<std::byte>& buffer);
+bool write_file(std::string_view path, const std::string& buffer);
+} // namespace io
+
+namespace io
+{
+std::string_view physfs_error(void);
+} // namespace io