ona/source/oar.cpp

202 lines
5.3 KiB
C++

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<header const *>(archive_header_buffer)};
if (!coral::equals({archive_header->signature_magic, signature_identifier_length},
{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<entry const *>(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<coral::usize, coral::io_error> read(coral::slice<coral::u8> 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<coral::usize>((
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<coral::u64, coral::io_error> seek(coral::u64 offset) override {
if (!this->is_open()) return coral::io_error::unavailable;
return coral::io_error::unavailable;
}
coral::expected<coral::u64, coral::io_error> 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<void(coral::file_reader &)> 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<void(coral::file_writer &)> const & then) override {
// Read-only file system.
}
private:
coral::fs * backing_fs;
coral::path archive_path;
};
}