summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-02-02 22:49:01 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-02-02 22:49:01 +0100
commit0f22ed2ef160eef2da6080782943b22c9539650d (patch)
tree1b43aada2d0183273e79b4d1db7b1e60dd809855
parent4fe67b36e3bbe6ea5505ed7233c245be9374f0ce (diff)
wip: hacky file browser
-rw-r--r--app/mac/scene.mm191
-rw-r--r--lib/nes/app/CMakeLists.txt3
-rw-r--r--lib/nes/app/application.cc47
-rw-r--r--lib/nes/app/application.hh3
-rw-r--r--lib/nes/app/file-browser.hh58
-rw-r--r--lib/nes/app/graphics/renderer.hh7
-rw-r--r--lib/nes/app/ui/action.hh43
-rw-r--r--lib/nes/app/ui/screen-browser.cc79
-rw-r--r--lib/nes/app/ui/screen-browser.hh26
-rw-r--r--lib/nes/app/ui/screen-title.cc8
-rw-r--r--lib/nes/common/path-view.hh72
-rw-r--r--lib/nes/sys/cartridge.hh2
12 files changed, 477 insertions, 62 deletions
diff --git a/app/mac/scene.mm b/app/mac/scene.mm
index 031413f..f8e3bb5 100644
--- a/app/mac/scene.mm
+++ b/app/mac/scene.mm
@@ -3,12 +3,16 @@
#import "nes/app/input/input-device-keyboard.hh"
#import "nes/app/input/input-device-controller.hh"
#import "nes/app/input/input-buffer.hh"
+#import "nes/app/file-browser.hh"
#import "nes/common/display.hh"
+#import "nes/common/path-view.hh"
#import <memory>
+#import <iostream>
#import <optional>
#import <fcntl.h>
#import <unistd.h>
#import <termios.h>
+#import <dirent.h>
#import <sys/ioctl.h>
namespace
@@ -351,11 +355,195 @@ namespace
return true;
}
};
+
+ class file_browser_impl final : public nes::app::file_browser
+ {
+ std::vector<std::string> components_;
+ std::vector<std::string_view> component_views_;
+ DIR* it_{ nullptr };
+ nes::u32 it_pos_{ 0 };
+
+ public:
+ explicit file_browser_impl()
+ {
+ if (chdir("/") == -1)
+ {
+ perror("chdir");
+ std::abort();
+ }
+
+ it_ = opendir(".");
+ if (!it_)
+ {
+ perror("opendir");
+ std::abort();
+ }
+ }
+
+ ~file_browser_impl() override
+ {
+ closedir(it_);
+ }
+
+ auto get_path() const -> nes::path_view override
+ {
+ return nes::path_view{ component_views_.data(), static_cast<nes::u32>(component_views_.size()) };
+ }
+
+ auto read_item_count() -> nes::u32 override
+ {
+ while (read_next())
+ ;
+ return it_pos_;
+ }
+
+ auto read_items(nes::u32 offset, item* buffer, nes::u32 buffer_size) -> nes::u32 override
+ {
+ seek(offset);
+ auto count = nes::u32{ 0 };
+ for (auto i = nes::u32{ 0 }; i < buffer_size; ++i)
+ {
+ auto item = read_next();
+ if (!item) { break; }
+ buffer[count++] = *item;
+ }
+
+ return count;
+ }
+
+ auto navigate_up() -> void override
+ {
+ if (components_.empty()) { return; }
+
+ components_.erase(components_.begin() + static_cast<std::ptrdiff_t>(components_.size() - 1));
+ component_views_.erase(component_views_.begin() + static_cast<std::ptrdiff_t>(component_views_.size() - 1));
+ auto path = std::string{};
+ for (auto const component : get_path())
+ {
+ path += "/";
+ path += component;
+ }
+ path += "/";
+
+ if (chdir(path.c_str()) == -1)
+ {
+ perror("chdir");
+ std::abort();
+ }
+
+ reopen_dir();
+ }
+
+ auto navigate(std::string_view const item) -> void override
+ {
+ auto item_copy = std::string{ item };
+ if (chdir(item_copy.c_str()) == -1)
+ {
+ perror("chdir");
+ std::abort();
+ }
+
+ components_.emplace_back(std::move(item_copy));
+ component_views_.clear();
+ for (auto& str : components_) { component_views_.emplace_back(str); }
+
+ reopen_dir();
+ }
+
+ auto load(std::string_view const item, nes::u8* buffer, nes::u32 const buffer_size) -> nes::u32 override
+ {
+ auto item_copy = std::string{ item };
+ auto const fd = open(item_copy.c_str(), O_RDONLY | O_NOCTTY | O_NDELAY | O_NONBLOCK);
+ if (fd == -1)
+ {
+ perror("open");
+ std::abort();
+ }
+
+ auto const res = read(fd, buffer, buffer_size);
+ if (res < 0)
+ {
+ perror("read");
+ std::abort();
+ }
+
+ auto remaining = char{};
+ if (read(fd, &remaining, 1) != 0)
+ {
+ std::cerr << "Expected EOF" << std::endl;
+ std::abort();
+ }
+
+ close(fd);
+
+ return static_cast<nes::u32>(res);
+ }
+
+ private:
+ auto reopen_dir() -> void
+ {
+ closedir(it_);
+ it_ = opendir(".");
+ if (!it_)
+ {
+ perror("opendir");
+ std::abort();
+ }
+ it_pos_ = 0;
+ }
+
+ auto seek(nes::u32 const position) -> void
+ {
+ if (it_pos_ > position)
+ {
+ reopen_dir();
+ }
+
+ while (it_pos_ < position)
+ {
+ if (!read_next()) { break; }
+ }
+ }
+
+ auto read_next() -> std::optional<item>
+ {
+ if (!it_) { return std::nullopt; }
+
+ while (true)
+ {
+ errno = 0;
+ auto entry = readdir(it_);
+ if (!entry)
+ {
+ if (errno)
+ {
+ perror("readdir");
+ std::abort();
+ }
+
+ return std::nullopt;
+ }
+
+ auto const name = std::string_view{ entry->d_name };
+ if (name.length() > max_name_length) { continue; }
+ if (name[0] == '.') { continue; }
+
+ auto type = item_type::directory;
+ if (entry->d_type == DT_DIR) { type = item_type::directory; }
+ else if (entry->d_type == DT_REG) { type = item_type::file; }
+ else { continue; }
+
+ it_pos_ += 1;
+ return item{ type, name };
+ }
+ }
+ };
} // namespace
@implementation Scene
{
std::unique_ptr<display_impl> _display;
+ std::unique_ptr<file_browser_impl> _fileBrowser;
std::unique_ptr<input_device_keyboard_impl> _keyboard;
std::unique_ptr<input_device_controller_serial> _serialController;
std::vector<std::unique_ptr<input_device_controller_impl>> _controllers;
@@ -376,9 +564,10 @@ namespace
[self addChild:node];
_display = std::make_unique<display_impl>(node);
+ _fileBrowser = std::make_unique<file_browser_impl>();
_keyboard = std::make_unique<input_device_keyboard_impl>([[GCKeyboard coalescedKeyboard] keyboardInput]);
_serialController = std::make_unique<input_device_controller_serial>("/dev/cu.usbserial-FTB6SPL3");
- _app = std::make_unique<nes::app::application>(*_display, *_keyboard);
+ _app = std::make_unique<nes::app::application>(*_display, *_keyboard, *_fileBrowser);
_app->add_controller(*_serialController);
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt
index 76ccb74..6e5aa93 100644
--- a/lib/nes/app/CMakeLists.txt
+++ b/lib/nes/app/CMakeLists.txt
@@ -2,7 +2,8 @@ target_sources(
nes
PRIVATE
application.hh
- application.cc)
+ application.cc
+ file-browser.hh)
add_subdirectory(input)
add_subdirectory(graphics)
diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc
index 5f73e01..5471984 100644
--- a/lib/nes/app/application.cc
+++ b/lib/nes/app/application.cc
@@ -4,16 +4,10 @@
#include "nes/app/ui/screen.hh"
#include "nes/app/graphics/renderer.hh"
#include "ui/action.hh"
-#include <fstream>
#include <iostream>
namespace nes::app
{
- namespace
- {
- constexpr auto file = std::string_view{ "/Users/hannes/Documents/temp/supermario.nes" };
- } // namespace
-
auto application::display_proxy::switch_buffers() -> void
{
if (screen)
@@ -25,11 +19,12 @@ namespace nes::app
base.switch_buffers();
}
- application::application(display& display, input_device_keyboard& keyboard)
+ application::application(display& display, input_device_keyboard& keyboard, file_browser& file_browser)
: display_{ display }
, input_manager_{ keyboard }
+ , file_browser_{ file_browser }
, screen_title_{ keyboard }
- , screen_browser_{ keyboard }
+ , screen_browser_{ keyboard, file_browser }
{
display_.screen = &screen_title_;
}
@@ -67,40 +62,40 @@ namespace nes::app
auto application::handle_action(action const a) -> void
{
- switch (a)
+ switch (a.get_type())
{
- case action::none:
+ case action::type::none:
{
break;
}
- case action::go_to_title:
+ case action::type::go_to_title:
{
console_.clear();
display_.screen = &screen_title_;
break;
}
- case action::go_to_browser:
+ case action::type::go_to_browser:
{
console_.clear();
display_.screen = &screen_browser_;
break;
}
- case action::launch_game:
+ case action::type::go_to_settings:
{
- auto stream = std::ifstream{ file, std::ios::binary };
- if (!stream.good())
- {
- std::cerr << "Unable to open cartridge file!" << std::endl;
- std::abort();
- }
-
- stream.seekg(0, std::ios::end);
- auto const length = stream.tellg();
- stream.seekg(0, std::ios::beg);
- auto data = std::vector<u8>(static_cast<u32>(length));
- stream.read(reinterpret_cast<char*>(data.data()), length);
+ // TODO
+ break;
+ }
+ case action::type::go_to_help:
+ {
+ // TODO
+ break;
+ }
+ case action::type::launch_game:
+ {
+ nes::u8 buffer[sys::cartridge::max_file_size];
+ auto const length = file_browser_.load(a.get_file_name(), buffer, sizeof(buffer));
- console_.emplace(display_, data.data(), static_cast<u32>(length));
+ console_.emplace(display_, buffer, length);
if (console_->get_status() != sys::status::success)
{
std::cerr << "Unable to load cartridge: " << to_string(console_->get_status()) << std::endl;
diff --git a/lib/nes/app/application.hh b/lib/nes/app/application.hh
index 44b99bb..724f2b6 100644
--- a/lib/nes/app/application.hh
+++ b/lib/nes/app/application.hh
@@ -41,12 +41,13 @@ namespace nes::app
display_proxy display_;
input_manager input_manager_;
+ file_browser& file_browser_;
box<sys::nes> console_{};
screen_title screen_title_;
screen_browser screen_browser_;
public:
- explicit application(display&, input_device_keyboard&);
+ explicit application(display&, input_device_keyboard&, file_browser& file_browser);
application(application const&) = delete;
application(application&&) = delete;
diff --git a/lib/nes/app/file-browser.hh b/lib/nes/app/file-browser.hh
new file mode 100644
index 0000000..b766f74
--- /dev/null
+++ b/lib/nes/app/file-browser.hh
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "nes/common/types.hh"
+
+namespace nes
+{
+ class path_view;
+} // namespace nes;
+
+namespace nes::app
+{
+ class file_browser
+ {
+ public:
+ static constexpr auto max_name_length = u32{ 64 };
+
+ enum class item_type
+ {
+ directory,
+ file,
+ };
+
+ class item
+ {
+ item_type type_{ item_type::directory };
+ char name_[max_name_length + 1]{};
+
+ public:
+ item() = default;
+
+ explicit item(item_type const type, std::string_view const name)
+ : type_{ type }
+ {
+ std::copy_n(name.begin(), std::min(static_cast<u32>(name.length()), max_name_length), name_);
+ }
+
+ auto get_type() const -> item_type { return type_; }
+ auto get_name() const -> std::string_view { return name_; }
+ };
+
+ virtual ~file_browser() = default;
+
+ file_browser(file_browser const&) = delete;
+ file_browser(file_browser&&) = delete;
+ auto operator=(file_browser const&) -> file_browser& = delete;
+ auto operator=(file_browser&&) -> file_browser& = delete;
+
+ virtual auto get_path() const -> path_view = 0;
+ virtual auto read_item_count() -> u32 = 0;
+ virtual auto read_items(u32 offset, item* buffer, u32 buffer_size) -> u32 = 0;
+ virtual auto navigate_up() -> void = 0;
+ virtual auto navigate(std::string_view) -> void = 0;
+ virtual auto load(std::string_view, u8* buffer, u32 buffer_size) -> u32 = 0;
+
+ protected:
+ explicit file_browser() = default;
+ };
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh
index cc0b9af..5b9ec48 100644
--- a/lib/nes/app/graphics/renderer.hh
+++ b/lib/nes/app/graphics/renderer.hh
@@ -54,18 +54,19 @@ namespace nes::app
template<typename... Args>
auto render_text_format(
i32 const x, i32 const y, color const c, text_attributes const attrs, std::string_view const fmt,
- Args... args) -> void
+ Args... args) -> u32
{
char buffer[width];
auto const str = string_builder{ buffer, sizeof(buffer) }.append_format(fmt, args...).get_result();
render_text(x, y, str, c, attrs);
+ return static_cast<u32>(str.length());
}
template<typename... Args>
auto render_text_format(
- i32 const x, i32 const y, color const c, std::string_view const fmt, Args... args) -> void
+ i32 const x, i32 const y, color const c, std::string_view const fmt, Args... args) -> u32
{
- render_text_format(x, y, c, text_attributes{}, fmt, args...);
+ return render_text_format(x, y, c, text_attributes{}, fmt, args...);
}
/// Render a rectangle at the given tile coordinates with a given size (measured in tiles).
diff --git a/lib/nes/app/ui/action.hh b/lib/nes/app/ui/action.hh
index 52e5ba6..b701606 100644
--- a/lib/nes/app/ui/action.hh
+++ b/lib/nes/app/ui/action.hh
@@ -1,15 +1,44 @@
#pragma once
+#include <string_view>
+
namespace nes::app
{
/// An action for the application to process after a scene has handled its events.
- enum class action
+ class action
{
- none,
- go_to_title,
- go_to_browser,
- go_to_settings,
- go_to_help,
- launch_game,
+ public:
+ enum class type
+ {
+ none,
+ go_to_title,
+ go_to_browser,
+ go_to_settings,
+ go_to_help,
+ launch_game,
+ };
+
+ private:
+ type type_{ type::none };
+ std::string_view file_name_;
+
+ public:
+ explicit action() = default;
+
+ static auto go_to_title() -> action { return action{ type::go_to_title, "" }; }
+ static auto go_to_browser() -> action { return action{ type::go_to_browser, "" }; }
+ static auto go_to_settings() -> action { return action{ type::go_to_settings, "" }; }
+ static auto go_to_help() -> action { return action{ type::go_to_help, "" }; }
+ static auto launch_game(std::string_view const file_name) -> action { return action{ type::launch_game, file_name }; }
+
+ auto get_type() const -> type { return type_; }
+ auto get_file_name() const -> std::string_view { return file_name_; }
+
+ private:
+ explicit action(type const type, std::string_view const file_name)
+ : type_{ type }
+ , file_name_{ file_name }
+ {
+ }
};
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-browser.cc b/lib/nes/app/ui/screen-browser.cc
index da2d82d..daca18e 100644
--- a/lib/nes/app/ui/screen-browser.cc
+++ b/lib/nes/app/ui/screen-browser.cc
@@ -1,30 +1,53 @@
#include "nes/app/ui/screen-browser.hh"
#include "nes/app/graphics/renderer.hh"
#include "nes/app/graphics/color.hh"
+#include "nes/app/graphics/tiles/icons.hh"
#include "nes/app/input/input-device-keyboard.hh"
#include "nes/common/string-builder.hh"
+#include "nes/common/path-view.hh"
namespace nes::app
{
auto screen_browser::selection_impl::render_item(
- renderer& renderer, i32 const& item, i32 const x, i32 const y, u32 const width, color const color) const -> void
+ renderer& renderer, item const& item, i32 const x, i32 const y, u32 const width,
+ color const color) const -> void
{
- renderer.render_text_format(x, y, color, text_attributes{}.set_max_width(width), "Item {}", item);
+ switch (item.data.get_type())
+ {
+ case file_browser::item_type::file: renderer.render_mask_tile(x, y, tiles::icon_cartridge, color); break;
+ case file_browser::item_type::directory: renderer.render_mask_tile(x, y, tiles::icon_directory, color); break;
+ }
+
+ renderer.render_text(x + 2, y, item.data.get_name(), color, text_attributes{}
+ .set_ellipsize_mode(ellipsize_mode::end)
+ .set_max_width(width - 2));
}
- auto screen_browser::selection_impl::load_page(int (&items)[5], u32 const page) -> u32
+ auto screen_browser::selection_impl::load_page(item (&items)[8], u32 const page) -> u32
{
- for (auto i = u32{ 0 }; i < 5; ++i)
+ auto offset = u32{ 0 };
+ if (page == 0 && file_browser_.get_path().get_component_count() > 0)
+ {
+ items[0].is_parent = true;
+ items[0].data = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" };
+ offset = 1;
+ }
+ file_browser::item buffer[8];
+ auto const count = file_browser_.read_items(page * 8, buffer, 8 - offset);
+ for (auto i = u32{ 0 }; i < count; ++i)
{
- items[i] = static_cast<i32>(page * 5 + i);
+ items[i + offset].is_parent = false;
+ items[i + offset].data = buffer[i];
}
- return 5;
+ return count + offset;
}
- screen_browser::screen_browser(input_device_keyboard& keyboard)
+ screen_browser::screen_browser(input_device_keyboard& keyboard, file_browser& file_browser)
: keyboard_{ keyboard }
+ , file_browser_{ file_browser }
+ , selection_{ file_browser }
{
- selection_.set_page_count(2);
+ selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8));
}
auto screen_browser::render(renderer& renderer) -> void
@@ -32,11 +55,21 @@ namespace nes::app
renderer.render_fill(color::background_primary);
renderer.render_rect(0, 0, 32, 3, color::background_secondary);
- renderer.render_text(2, 1, "Load Game", color::foreground_primary);
- renderer.render_text_format(
+ auto const page_length = renderer.render_text_format(
30, 1, color::foreground_secondary, text_attributes{}.set_alignment(text_alignment::right),
"Page {}/{}", selection_.get_current_page() + 1, selection_.get_page_count());
+ char path_buffer[32];
+ auto path_builder = string_builder{ path_buffer, sizeof(path_buffer) };
+ for (auto const component : file_browser_.get_path())
+ {
+ path_builder.append_format("> {} ", component);
+ }
+ path_builder.append_string(">");
+ renderer.render_text(
+ 2, 1, path_builder.get_result(), color::foreground_primary,
+ text_attributes{}.set_max_width(26 - page_length).set_ellipsize_mode(ellipsize_mode::end));
+
selection_.render(renderer);
}
@@ -58,14 +91,34 @@ namespace nes::app
}
else if (event == input_event::key_down(key::backspace))
{
- return action::go_to_title;
+ return action::go_to_title();
}
else if (event == input_event::key_down(key::enter))
{
- return action::launch_game;
+ auto selected = selection_.get_selected();
+ if (selected)
+ {
+ if (selected->is_parent)
+ {
+ file_browser_.navigate_up();
+ selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8));
+ }
+ else
+ {
+ switch (selected->data.get_type())
+ {
+ case file_browser::item_type::directory:
+ file_browser_.navigate(selected->data.get_name());
+ selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8));
+ break;
+ case file_browser::item_type::file:
+ return action::launch_game(selected->data.get_name());
+ }
+ }
+ }
}
}
- return action::none;
+ return action{};
}
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-browser.hh b/lib/nes/app/ui/screen-browser.hh
index eb66266..7a711c5 100644
--- a/lib/nes/app/ui/screen-browser.hh
+++ b/lib/nes/app/ui/screen-browser.hh
@@ -2,6 +2,7 @@
#include "nes/app/ui/screen.hh"
#include "nes/app/ui/selection.hh"
+#include "nes/app/file-browser.hh"
namespace nes::app
{
@@ -10,21 +11,34 @@ namespace nes::app
/// An interface which allows the user to browse the filesystem for a game.
class screen_browser final : public screen
{
- class selection_impl final : public selection<i32, 5>
+ struct item
{
+ bool is_parent{ false };
+ file_browser::item data{ file_browser::item_type::directory, "" };
+ };
+
+ class selection_impl final : public selection<item, 8>
+ {
+ file_browser& file_browser_;
+
public:
- using selection::selection;
+ explicit selection_impl(file_browser& file_browser)
+ : selection{ 2, 5, 28 }
+ , file_browser_{ file_browser }
+ {
+ }
protected:
- auto render_item(renderer&, i32 const&, i32 x, i32 y, u32 width, color) const -> void override;
- auto load_page(i32(&)[5], u32 page) -> u32 override;
+ auto render_item(renderer&, item const&, i32 x, i32 y, u32 width, color) const -> void override;
+ auto load_page(item(&)[8], u32 page) -> u32 override;
};
input_device_keyboard& keyboard_;
- selection_impl selection_{ 2, 5, 28 };
+ file_browser& file_browser_;
+ selection_impl selection_;
public:
- explicit screen_browser(input_device_keyboard&);
+ explicit screen_browser(input_device_keyboard&, file_browser&);
auto render(renderer&) -> void override;
auto process_events() -> action override;
diff --git a/lib/nes/app/ui/screen-title.cc b/lib/nes/app/ui/screen-title.cc
index 32a6ea6..15cd601 100644
--- a/lib/nes/app/ui/screen-title.cc
+++ b/lib/nes/app/ui/screen-title.cc
@@ -61,13 +61,13 @@ namespace nes::app
{
switch (*selection_.get_selected())
{
- case item::load_game: return action::go_to_browser;
- case item::settings: return action::go_to_settings;
- case item::help: return action::go_to_help;
+ case item::load_game: return action::go_to_browser();
+ case item::settings: return action::go_to_settings();
+ case item::help: return action::go_to_help();
}
}
}
- return action::none;
+ return action{};
}
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/common/path-view.hh b/lib/nes/common/path-view.hh
new file mode 100644
index 0000000..3d670bf
--- /dev/null
+++ b/lib/nes/common/path-view.hh
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "nes/common/types.hh"
+#include <string_view>
+
+namespace nes
+{
+ /// Non-owning copy of a path, consisting of multiple path components stored as string views.
+ ///
+ /// This is similar in spirit to a string_view, but for collections of string_views instead of characters.
+ class path_view
+ {
+ std::string_view const* components_{ nullptr };
+ u32 component_count_{ 0 };
+
+ public:
+ /// An iterator for the path components.
+ class iterator
+ {
+ friend path_view;
+
+ std::string_view const* current_{ nullptr };
+ u32 remaining_{ 0 };
+
+ public:
+ auto operator==(iterator const& other) -> bool { return current_ == other.current_; }
+ auto operator!=(iterator const& other) -> bool { return current_ != other.current_; }
+
+ auto operator*() const -> std::string_view { return current_ ? *current_ : ""; }
+
+ auto operator++() -> iterator&
+ {
+ if (remaining_ > 0)
+ {
+ current_ += 1;
+ remaining_ -= 1;
+ }
+ return *this;
+ }
+
+ auto operator++(int) -> iterator
+ {
+ auto res = *this;
+ ++*this;
+ return res;
+ }
+
+ private:
+ explicit iterator() = default;
+ explicit iterator(std::string_view const* current, u32 const remaining)
+ : current_{ current }
+ , remaining_{ remaining }
+ {
+ }
+ };
+
+ explicit path_view() = default;
+
+ explicit path_view(std::string_view const* components, u32 const component_count)
+ : components_{ components }
+ , component_count_{ component_count }
+ {
+ }
+
+ auto get_components() const -> std::string_view const* { return components_; }
+ auto get_component_count() const -> u32 { return component_count_; }
+ auto get_component(u32 const index) const -> std::string_view { return components_[index]; }
+
+ auto begin() const -> iterator { return iterator{ components_, component_count_ }; }
+ auto end() const -> iterator { return iterator{ components_ + component_count_, 0 }; }
+ };
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/sys/cartridge.hh b/lib/nes/sys/cartridge.hh
index 2cfb114..1934b8d 100644
--- a/lib/nes/sys/cartridge.hh
+++ b/lib/nes/sys/cartridge.hh
@@ -62,6 +62,8 @@ namespace nes::sys
mapper* mapper_{ &mapper::invalid() };
public:
+ static constexpr auto max_file_size = u32{ max_prg_rom_size + max_chr_rom_size + 512 + header_length };
+
explicit cartridge(u8 const* data, u32 length);
auto get_status() const -> status { return status_; }