module; #include export module app; import coral; import coral.files; import coral.image; import coral.io; import coral.math; import oar; using native_path = coral::fixed_buffer<4096>; struct native_file : public coral::file_reader, public coral::file_writer { enum class open_mode { read_only, overwrite, }; enum class [[nodiscard]] close_result { ok, io_unavailable, }; enum class [[nodiscard]] open_result { ok, io_unavailable, access_denied, not_found, }; native_file() = default; close_result close() { if (SDL_RWclose(this->rw_ops) != 0) return close_result::io_unavailable; this->rw_ops = nullptr; return close_result::ok; } bool is_open() const { return this->rw_ops != nullptr; } open_result open(native_path const & file_path, open_mode mode) { if (this->is_open()) switch (this->close()) { case close_result::ok: break; case close_result::io_unavailable: return open_result::io_unavailable; default: coral::unreachable(); } // No room for zero terminator. if (file_path.is_full()) return open_result::not_found; switch (mode) { case open_mode::read_only: { this->rw_ops = SDL_RWFromFile(file_path.as_slice().as_chars().begin(), "rb"); break; } case open_mode::overwrite: { this->rw_ops = SDL_RWFromFile(file_path.as_slice().as_chars().begin(), "wb"); break; } default: coral::unreachable(); } if (this->rw_ops == nullptr) return open_result::not_found; return open_result::ok; } coral::expected read(coral::slice const & data) override { if (!this->is_open()) return coral::io_error::unavailable; coral::usize const data_read{SDL_RWread(this->rw_ops, data.pointer, sizeof(uint8_t), data.length)}; if ((data_read == 0) && (SDL_GetError() != nullptr)) return coral::io_error::unavailable; return data_read; } coral::expected seek(coral::u64 offset) override { if (!this->is_open()) return coral::io_error::unavailable; // TODO: Fix cast. coral::i64 const byte_position{ SDL_RWseek(this->rw_ops, static_cast(offset), RW_SEEK_SET)}; if (byte_position == -1) return coral::io_error::unavailable; return static_cast(byte_position); } coral::expected tell() override { if (!this->is_open()) return coral::io_error::unavailable; coral::i64 const byte_position{SDL_RWseek(this->rw_ops, 0, RW_SEEK_SET)}; if (byte_position == -1) return coral::io_error::unavailable; return static_cast(byte_position); } coral::expected write(coral::slice const & data) override { if (!this->is_open()) return coral::io_error::unavailable; coral::usize const data_written{SDL_RWwrite(this->rw_ops, data.pointer, sizeof(uint8_t), data.length)}; if ((data_written == 0) && (SDL_GetError() != nullptr)) return coral::io_error::unavailable; return data_written; } private: SDL_RWops * rw_ops{nullptr}; }; struct sandboxed_fs : public coral::fs { sandboxed_fs() { char * const path{SDL_GetBasePath()}; if (path == nullptr) return; for (coral::usize index = 0; path[index] != 0; index += 1) this->sandbox_path.put(path[index]); SDL_free(path); this->access_rules.can_read = true; } sandboxed_fs(coral::path const & organization_name, coral::path const & app_name) { char * const path{SDL_GetPrefPath(organization_name.begin(), app_name.begin())}; if (path == nullptr) return; for (coral::usize index = 0; path[index] != 0; index += 1) this->sandbox_path.put(path[index]); SDL_free(path); this->access_rules.can_read = true; } access_rules query_access() override { return this->access_rules; } void read_file(coral::path const & file_path, coral::callable const & then) override { if (!this->access_rules.can_read) return; native_path sandbox_file_path{0}; { coral::expected const written = sandbox_file_path.write(this->sandbox_path.as_slice()); if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return; } { coral::expected const written = sandbox_file_path.write(file_path.as_slice().as_bytes()); if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return; } native_file file; if (file.open(sandbox_file_path, native_file::open_mode::read_only) != native_file::open_result::ok) return; then(file); if (file.close() != native_file::close_result::ok) // Error orphaned file handle! return; } void write_file(coral::path const & file_path, coral::callable const & then) override { if (!this->access_rules.can_write) return; native_path sandbox_file_path{0}; { coral::expected const written = sandbox_file_path.write(this->sandbox_path.as_slice()); if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return; } { coral::expected const written = sandbox_file_path.write(file_path.as_slice().as_bytes()); if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return; } native_file file; if (file.open(sandbox_file_path, native_file::open_mode::overwrite) != native_file::open_result::ok) return; then(file); if (file.close() != native_file::close_result::ok) // Error orphaned file handle! return; } private: native_path sandbox_path{0}; access_rules access_rules{ .can_read = false, .can_write = false, }; }; export namespace app { enum class log_level { notice, warning, error, }; struct client { coral::fs & base() { return this->base_sandbox; } void display(coral::u16 screen_width, coral::u16 screen_height) { SDL_SetWindowSize(this->window, screen_width, screen_height); SDL_ShowWindow(this->window); } void log(log_level level, coral::slice const & message) { coral::i32 const length{static_cast( coral::min(message.length, static_cast(coral::i32_max)))}; SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); } bool poll() { while (SDL_PollEvent(&this->event) != 0) { switch (this->event.type) { case SDL_QUIT: return false; } } return true; } coral::fs & resources() { return this->resources_archive; } static int run(coral::path const & title, coral::callable const & start) { constexpr int windowpos {SDL_WINDOWPOS_UNDEFINED}; constexpr coral::u32 windowflags {SDL_WINDOW_HIDDEN}; constexpr int window_width {640}; constexpr int window_height {480}; SDL_Window * const window {SDL_CreateWindow( title.begin(), windowpos, windowpos, window_width, window_height, windowflags)}; if (window == nullptr) return 0xff; struct : public coral::allocator { coral::u8 * reallocate(coral::u8 * allocation, coral::usize requested_size) override { return reinterpret_cast(SDL_realloc(allocation, requested_size)); } void deallocate(void * allocation) override { SDL_free(allocation); } } allocator; client app_client {&allocator, window, title}; return start(app_client); } coral::allocator & thread_safe_allocator() { return *this->allocator; } coral::fs & user() { return this->user_sandbox; } private: client(coral::allocator * allocator, SDL_Window * window, coral::path const & title) : user_sandbox{"ona", title} { this->allocator = allocator; this->window = window; } coral::allocator * allocator; SDL_Window * window; SDL_Event event; sandboxed_fs base_sandbox; sandboxed_fs user_sandbox; oar::archive resources_archive{&base_sandbox, "base.oar"}; }; }