export module oar; import coral; import coral.files; constexpr coral::usize signature_length{4}; constexpr coral::usize signature_version_length{1}; constexpr coral::usize signature_identifier_length{signature_length - signature_version_length}; constexpr coral::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; struct header { coral::u8 signature_magic[signature_length]; coral::u32 entry_count; coral::u8 padding[504]; }; static_assert(sizeof(header) == 512); struct entry { coral::path path; coral::u64 data_offset; coral::u64 data_length; coral::u8 padding[240]; }; static_assert(sizeof(entry) == 512); export namespace oar { struct archive_file_reader : public coral::file_reader { enum class [[nodiscard]] close_result { ok, }; enum class [[nodiscard]] open_result { ok, io_unavailable, archive_invalid, archive_unsupported, not_found, }; archive_file_reader(coral::file_reader * archive_reader) { this->archive_reader = archive_reader; this->data_offset = 0; this->data_length = 0; this->data_cursor = 0; } close_result close() { return close_result::ok; } bool is_open() const { return this->data_offset >= sizeof(header); } open_result open(coral::path const & file_path) { if (this->is_open()) switch (this->close()) { case close_result::ok: break; default: coral::unreachable(); } if (!this->archive_reader->seek(0).is_ok()) return open_result::io_unavailable; constexpr coral::usize header_size = sizeof(header); coral::u8 archive_header_buffer[header_size]{0}; { coral::expected const read_bytes{archive_reader->read(archive_header_buffer)}; if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) return open_result::archive_invalid; } header const * const archive_header{reinterpret_cast
(archive_header_buffer)}; if (!coral::equals(coral::slice{archive_header->signature_magic, signature_identifier_length}, coral::slice{signature_magic, signature_identifier_length})) return open_result::archive_invalid; if (archive_header->signature_magic[signature_identifier_length] != signature_magic[signature_identifier_length]) return open_result::archive_unsupported; // Read file table. coral::u64 head{0}; coral::u64 tail{archive_header->entry_count - 1}; constexpr coral::usize entry_size{sizeof(entry)}; coral::u8 file_entry_buffer[entry_size]{0}; while (head <= tail) { coral::u64 const midpoint{head + ((tail - head) / 2)}; if (!archive_reader->seek(header_size + (entry_size * midpoint)).is_ok()) return open_result::archive_invalid; { coral::expected const read_bytes{archive_reader->read(file_entry_buffer)}; if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) return open_result::archive_invalid; } entry const * const archive_entry{reinterpret_cast(file_entry_buffer)}; coral::size const comparison{file_path.compare(archive_entry->path)}; if (comparison == 0) { this->data_offset = archive_entry->data_offset; this->data_length = archive_entry->data_length; this->data_cursor = archive_entry->data_offset; return open_result::ok; } if (comparison > 0) { head = (midpoint + 1); } else { tail = (midpoint - 1); } } return open_result::not_found; } coral::expected read(coral::slice const & buffer) override { if (!this->is_open()) return coral::io_error::unavailable; if (this->archive_reader->seek(this->data_offset + this->data_cursor).is_ok()) return coral::io_error::unavailable; coral::expected const bytes_read{this->archive_reader->read(buffer.sliced(0, coral::min(buffer.length, static_cast(( this->data_offset + this->data_length) - this->data_cursor))))}; if (!bytes_read.is_ok()) this->data_cursor += bytes_read.value(); return bytes_read; } coral::expected seek(coral::u64 offset) override { if (!this->is_open()) return coral::io_error::unavailable; return coral::io_error::unavailable; } coral::expected tell() override { if (!this->is_open()) return coral::io_error::unavailable; return this->data_cursor; } private: coral::file_reader * archive_reader; coral::u64 data_offset; coral::u64 data_length; coral::u64 data_cursor; }; struct archive : public coral::fs { archive(coral::fs * backing_fs, coral::path const & archive_path) { this->backing_fs = backing_fs; this->archive_path = archive_path; } void read_file(coral::path const & file_path, coral::callable const & then) override { if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return; this->backing_fs->read_file(this->archive_path, [&](coral::file_reader & archive_reader) { archive_file_reader file_reader{&archive_reader}; if (file_reader.open(file_path) != archive_file_reader::open_result::ok) return; then(file_reader); if (file_reader.close() != archive_file_reader::close_result::ok) return; }); } void write_file(coral::path const & file_path, coral::callable const & then) override { // Read-only file system. } private: coral::fs * backing_fs; coral::path archive_path; }; }