summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.mm36
-rw-r--r--src/nes/CMakeLists.txt2
-rw-r--r--src/nes/cartridge.cc65
-rw-r--r--src/nes/cartridge.hh33
-rw-r--r--src/nes/nes.cc12
-rw-r--r--src/nes/nes.hh6
-rw-r--r--src/nes/util/CMakeLists.txt1
-rw-r--r--src/nes/util/address.hh40
-rw-r--r--src/scene.hh2
-rw-r--r--src/scene.mm8
10 files changed, 184 insertions, 21 deletions
diff --git a/src/main.mm b/src/main.mm
index 5207e62..5ca7c5c 100644
--- a/src/main.mm
+++ b/src/main.mm
@@ -1,44 +1,52 @@
#import "scene.hh"
+#import <iostream>
#import <Cocoa/Cocoa.h>
#import <SpriteKit/SpriteKit.h>
int main(int argc, char** argv)
{
- NSApplication* app = [NSApplication sharedApplication];
+ if (argc != 2)
+ {
+ std::cerr << "Usage: " << argv[0] << " <rom-path>" << std::endl;
+ return 1;
+ }
+
+ auto filePath = [NSString stringWithUTF8String:argv[1]];
+
+ auto app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
- NSMenuItem* quitItem = [[NSMenuItem alloc] init];
+ auto quitItem = [[NSMenuItem alloc] init];
[quitItem setTitle:@"Quit NES"];
[quitItem setKeyEquivalent:@"q"];
[quitItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
[quitItem setTarget:app];
[quitItem setAction:@selector(terminate:)];
- NSMenu* menu = [[NSMenu alloc] init];
+ auto menu = [[NSMenu alloc] init];
[menu addItem:quitItem];
- NSMenuItem* item = [[NSMenuItem alloc] init];
+ auto item = [[NSMenuItem alloc] init];
[item setSubmenu:menu];
- NSMenu* mainMenu = [[NSMenu alloc] init];
+ auto mainMenu = [[NSMenu alloc] init];
[mainMenu addItem:item];
[app setMainMenu:mainMenu];
- Scene* scene = [[Scene alloc] init];
- CGSize minSize = [scene size];
-
+ auto scene = [[Scene alloc] initWithFilePath:filePath];
+ auto minSize = [scene size];
- SKView* view = [[SKView alloc] init];
+ auto view = [[SKView alloc] init];
[view setIgnoresSiblingOrder:YES];
[view setShowsFPS:YES];
[view presentScene:scene];
- NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0., 0., minSize.width * 2, minSize.height * 2)
- styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
- NSWindowMiniaturizeButton | NSWindowStyleMaskResizable)
- backing:NSBackingStoreBuffered
- defer:false];
+ auto window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0., 0., minSize.width * 2, minSize.height * 2)
+ styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
+ NSWindowMiniaturizeButton | NSWindowStyleMaskResizable)
+ backing:NSBackingStoreBuffered
+ defer:false];
[window setTitle:@"NES"];
[window setContentAspectRatio:minSize];
[window setContentMinSize:minSize];
diff --git a/src/nes/CMakeLists.txt b/src/nes/CMakeLists.txt
index 560dcdd..c217219 100644
--- a/src/nes/CMakeLists.txt
+++ b/src/nes/CMakeLists.txt
@@ -5,5 +5,7 @@ target_sources(
PRIVATE
nes.hh
nes.cc
+ cartridge.hh
+ cartridge.cc
display.hh
display.cc) \ No newline at end of file
diff --git a/src/nes/cartridge.cc b/src/nes/cartridge.cc
new file mode 100644
index 0000000..5970d2a
--- /dev/null
+++ b/src/nes/cartridge.cc
@@ -0,0 +1,65 @@
+#include "nes/cartridge.hh"
+#include <fstream>
+
+namespace
+{
+ constexpr auto prg_rom_bank_size = std::size_t{ 16 * 1024 };
+ constexpr auto chr_rom_bank_size = std::size_t{ 8 * 1024 };
+ constexpr auto ram_bank_size = std::size_t{ 8 * 1024 };
+} // namespace
+
+namespace nes
+{
+ auto cartridge::from_data(void const* data, std::size_t const length) -> cartridge
+ {
+ auto const bytes = static_cast<std::uint8_t const*>(data);
+ auto res = cartridge{};
+ res.is_valid_ = true;
+
+ // Parse header.
+ if (length < 16) { return cartridge{}; }
+ if (bytes[0] != 'N' || bytes[1] != 'E' || bytes[2] != 'S' || bytes[3] != 0x1A) { return cartridge{}; }
+ auto const prg_rom_banks = bytes[4];
+ auto const chr_rom_banks = bytes[5];
+ auto const control_1 = bytes[6];
+ auto const control_2 = bytes[7];
+ auto const ram_banks = (bytes[8] != 0) ? bytes[8] : 1;
+ auto const has_trainer = (control_1 & 0b00000100) != 0;
+
+ res.mapper_number_ =
+ (control_1 & 0b11110000) >> 4 |
+ (control_2 & 0b11110000) >> 0;
+
+ // Load program data.
+ auto offset = 16;
+ if (has_trainer) { offset += 512; }
+
+ auto const prg_rom_size = prg_rom_banks * prg_rom_bank_size;
+ if (length < offset + prg_rom_size) { return cartridge{}; }
+ res.prg_rom_ = std::vector(&bytes[offset], &bytes[offset + prg_rom_size]);
+
+ offset += prg_rom_size;
+
+ auto const chr_rom_size = chr_rom_banks * chr_rom_bank_size;
+ if (length < offset + chr_rom_size) { return cartridge{}; }
+ res.chr_rom_ = std::vector(&bytes[offset], &bytes[offset + chr_rom_size]);
+
+ res.ram_ = std::vector(ram_banks * ram_bank_size, std::uint8_t{ 0 });
+
+ return res;
+ }
+
+ auto cartridge::from_file(std::string_view const path) -> cartridge
+ {
+ auto stream = std::ifstream{ path, std::ios::binary };
+ if (!stream.good()) { return cartridge{}; }
+
+ stream.seekg(0, std::ios::end);
+ auto const length = stream.tellg();
+ stream.seekg(0, std::ios::beg);
+ auto data = std::vector<std::uint8_t>(length);
+ stream.read(reinterpret_cast<char*>(data.data()), data.size());
+
+ return from_data(data.data(), data.size());
+ }
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/cartridge.hh b/src/nes/cartridge.hh
new file mode 100644
index 0000000..a334125
--- /dev/null
+++ b/src/nes/cartridge.hh
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <cstddef>
+#include <vector>
+#include <string_view>
+
+namespace nes
+{
+ class cartridge
+ {
+ bool is_valid_{ false };
+ std::vector<std::uint8_t> prg_rom_;
+ std::vector<std::uint8_t> chr_rom_;
+ std::vector<std::uint8_t> ram_;
+ std::uint8_t mapper_number_{ 0 };
+
+ public:
+ explicit cartridge() = default;
+
+ static auto from_data(void const* data, std::size_t length) -> cartridge;
+ static auto from_file(std::string_view path) -> cartridge;
+
+ auto is_valid() const -> bool { return is_valid_; }
+ auto get_prg_rom() const -> std::uint8_t const* { return prg_rom_.data(); }
+ auto get_prg_rom_length() const -> std::size_t { return prg_rom_.size(); }
+ auto get_chr_rom() const -> std::uint8_t const* { return chr_rom_.data(); }
+ auto get_chr_rom_length() const -> std::size_t { return chr_rom_.size(); }
+ auto get_ram() const -> std::uint8_t const* { return ram_.data(); }
+ auto get_ram_mut() -> std::uint8_t* { return ram_.data(); }
+ auto get_ram_length() const -> std::size_t { return ram_.size(); }
+ auto get_mapper_number() const -> std::uint8_t { return mapper_number_; }
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/nes.cc b/src/nes/nes.cc
index c503d65..63175aa 100644
--- a/src/nes/nes.cc
+++ b/src/nes/nes.cc
@@ -1,12 +1,20 @@
#include "nes/nes.hh"
#include "nes/display.hh"
#include "nes/util/rgb.hh"
+#include <iostream>
namespace nes
{
- nes::nes(display& display)
- : display_{ display }
+ nes::nes(cartridge cartridge, display& display)
+ : cartridge_{ std::move(cartridge) }
+ , display_{ display }
{
+ if (!cartridge_.is_valid())
+ {
+ std::cerr << "Cartridge not valid!" << std::endl;
+ std::abort();
+ }
+
display.clear(rgb::from_hex(0xFF0000));
display.switch_buffers();
}
diff --git a/src/nes/nes.hh b/src/nes/nes.hh
index ab975f0..924ddd4 100644
--- a/src/nes/nes.hh
+++ b/src/nes/nes.hh
@@ -1,15 +1,19 @@
#pragma once
+#include "nes/cartridge.hh"
+
namespace nes
{
class display;
+ /// The main console abstraction.
class nes
{
+ cartridge cartridge_;
display& display_;
public:
- explicit nes(display&);
+ explicit nes(cartridge, display&);
nes(nes const&) = delete;
nes(nes&&) = delete;
diff --git a/src/nes/util/CMakeLists.txt b/src/nes/util/CMakeLists.txt
index c2255dd..1d30125 100644
--- a/src/nes/util/CMakeLists.txt
+++ b/src/nes/util/CMakeLists.txt
@@ -1,4 +1,5 @@
target_sources(
nes
PRIVATE
+ address.hh
rgb.hh) \ No newline at end of file
diff --git a/src/nes/util/address.hh b/src/nes/util/address.hh
new file mode 100644
index 0000000..f02b66b
--- /dev/null
+++ b/src/nes/util/address.hh
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <cstdint>
+
+namespace nes
+{
+ /// An address on the NES.
+ class address
+ {
+ std::uint16_t absolute_{ 0 };
+
+ public:
+ /// Create a zero address.
+ explicit address() = default;
+
+ /// Create an address from its absolute value.
+ explicit address(std::uint16_t const absolute)
+ : absolute_{ absolute }
+ {
+ }
+
+ /// Get the absolute address value.
+ auto get_absolute() const -> std::uint16_t
+ {
+ return absolute_;
+ }
+
+ /// Get the address page (the first 8 bits).
+ auto get_page() const -> std::uint8_t
+ {
+ return static_cast<std::uint8_t>((get_absolute() & 0xFF00) >> 8);
+ }
+
+ /// Get the offset within the page (the last 8 bits).
+ auto get_offset() const -> std::uint8_t
+ {
+ return static_cast<std::uint8_t>((get_absolute() & 0x00FF) >> 0);
+ }
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/scene.hh b/src/scene.hh
index 967a4fe..1660e66 100644
--- a/src/scene.hh
+++ b/src/scene.hh
@@ -4,7 +4,7 @@
@interface Scene : SKScene
-- (instancetype)init NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER;
+ (instancetype)sceneWithSize:(CGSize)size NS_UNAVAILABLE;
- (instancetype)initWithSize:(CGSize)size NS_UNAVAILABLE;
diff --git a/src/scene.mm b/src/scene.mm
index 944d01a..1dbcecd 100644
--- a/src/scene.mm
+++ b/src/scene.mm
@@ -70,20 +70,22 @@ namespace
std::unique_ptr<nes::nes> nes_;
}
-- (instancetype)init
+- (instancetype)initWithFilePath:(NSString*)filePath
{
self = [super initWithSize:CGSizeMake(nes::display::width, nes::display::height)];
if (self)
{
- SKSpriteNode* node = [[SKSpriteNode alloc] init];
+ auto node = [[SKSpriteNode alloc] init];
[node setAnchorPoint:CGPointZero];
[node setSize:[self size]];
[self setScaleMode:SKSceneScaleModeAspectFit];
[self addChild:node];
+ auto cartridge = nes::cartridge::from_file([filePath UTF8String]);
+
self->display_ = std::make_unique<display_impl>(node);
- self->nes_ = std::make_unique<nes::nes>(*self->display_);
+ self->nes_ = std::make_unique<nes::nes>(std::move(cartridge), *self->display_);
}
return self;
}