rnes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ module Rnes
2
+ class Ppu
3
+ COLORS = [
4
+ [0x52, 0x52, 0x52],
5
+ [0x01, 0x1a, 0x51],
6
+ [0x0f, 0x0f, 0x65],
7
+ [0x23, 0x06, 0x63],
8
+ [0x36, 0x03, 0x4b],
9
+ [0x40, 0x04, 0x26],
10
+ [0x3f, 0x09, 0x04],
11
+ [0x32, 0x13, 0x00],
12
+ [0x1f, 0x20, 0x00],
13
+ [0x0b, 0x2a, 0x00],
14
+ [0x00, 0x2f, 0x00],
15
+ [0x00, 0x2e, 0x0a],
16
+ [0x00, 0x26, 0x2d],
17
+ [0x00, 0x00, 0x00],
18
+ [0x00, 0x00, 0x00],
19
+ [0x00, 0x00, 0x00],
20
+ [0xa0, 0xa0, 0xa0],
21
+ [0x1e, 0x4a, 0x9d],
22
+ [0x38, 0x37, 0xbc],
23
+ [0x58, 0x28, 0xb8],
24
+ [0x75, 0x21, 0x94],
25
+ [0x84, 0x23, 0x5c],
26
+ [0x82, 0x2e, 0x24],
27
+ [0x6f, 0x3f, 0x00],
28
+ [0x51, 0x52, 0x00],
29
+ [0x31, 0x63, 0x00],
30
+ [0x1a, 0x6b, 0x05],
31
+ [0x0e, 0x69, 0x2e],
32
+ [0x10, 0x5c, 0x68],
33
+ [0x00, 0x00, 0x00],
34
+ [0x00, 0x00, 0x00],
35
+ [0x00, 0x00, 0x00],
36
+ [0xfe, 0xff, 0xff],
37
+ [0x69, 0x9e, 0xfc],
38
+ [0x89, 0x87, 0xff],
39
+ [0xae, 0x76, 0xff],
40
+ [0xce, 0x6d, 0xf1],
41
+ [0xe0, 0x70, 0xb2],
42
+ [0xde, 0x7c, 0x70],
43
+ [0xc8, 0x91, 0x3e],
44
+ [0xa6, 0xa7, 0x25],
45
+ [0x81, 0xba, 0x28],
46
+ [0x63, 0xc4, 0x46],
47
+ [0x54, 0xc1, 0x7d],
48
+ [0x56, 0xb3, 0xc0],
49
+ [0x3c, 0x3c, 0x3c],
50
+ [0x00, 0x00, 0x00],
51
+ [0x00, 0x00, 0x00],
52
+ [0xfe, 0xff, 0xff],
53
+ [0xbe, 0xd6, 0xfd],
54
+ [0xcc, 0xcc, 0xff],
55
+ [0xdd, 0xc4, 0xff],
56
+ [0xea, 0xc0, 0xf9],
57
+ [0xf2, 0xc1, 0xdf],
58
+ [0xf1, 0xc7, 0xc2],
59
+ [0xe8, 0xd0, 0xaa],
60
+ [0xd9, 0xda, 0x9d],
61
+ [0xc9, 0xe2, 0x9e],
62
+ [0xbc, 0xe6, 0xae],
63
+ [0xb4, 0xe5, 0xc7],
64
+ [0xb5, 0xdf, 0xe4],
65
+ [0xa9, 0xa9, 0xa9],
66
+ [0x00, 0x00, 0x00],
67
+ [0x00, 0x00, 0x00],
68
+ ].freeze
69
+ end
70
+ end
@@ -0,0 +1,60 @@
1
+ require 'rnes/errors'
2
+
3
+ module Rnes
4
+ class PpuBus
5
+ # @return [Rnes::Ram]
6
+ attr_reader :character_ram
7
+
8
+ # @param [Rnes::Ram] character_ram
9
+ # @param [Rnes::Ram] sprite_ram
10
+ # @param [Rnes::Ram] video_ram
11
+ def initialize(character_ram:, video_ram:)
12
+ @character_ram = character_ram
13
+ @video_ram = video_ram
14
+ end
15
+
16
+ # @param [Integer] address
17
+ # @return [Integer]
18
+ def read(address)
19
+ case address
20
+ when 0x0000..0x1FFF
21
+ @character_ram.read(address)
22
+ when 0x2000..0x27FF
23
+ @video_ram.read(address - 0x2000)
24
+ when 0x2800..0x2FFF
25
+ read(address - 0x0800)
26
+ when 0x3000..0x3EFF
27
+ read(address - 0x1000)
28
+ when 0x3F00..0x3F1F
29
+ @video_ram.read(address - 0x2000)
30
+ when 0x3F20..0x3FFF
31
+ read(address - 0x20) # mirror to 0x3F00..0x3F1F
32
+ when 0x4000..0xFFFF
33
+ read(address - 0x4000) # mirror to 0x0000..0x3FFF
34
+ else
35
+ raise ::Rnes::Errors::InvalidPpuBusAddressError, address
36
+ end
37
+ end
38
+
39
+ # @param [Integer] address
40
+ # @param [Integer] value
41
+ def write(address, value)
42
+ case address
43
+ when 0x0000..0x1FFF
44
+ @character_ram.write(address, value)
45
+ when 0x2000..0x27FF
46
+ @video_ram.write(address - 0x2000, value)
47
+ when 0x2800..0x2FFF
48
+ @video_ram.write(address - 0x0800, value)
49
+ when 0x3000..0x3EFF
50
+ @video_ram.write(address - 0x1000, value)
51
+ when 0x3F00..0x3F1F
52
+ @video_ram.write(address - 0x2000, value)
53
+ when 0x3F00..0xFFFF
54
+ write(address - 0x1000, value)
55
+ else
56
+ raise ::Rnes::Errors::InvalidPpuBusAddressError, address
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,111 @@
1
+ module Rnes
2
+ class PpuRegisters
3
+ STATUS_IN_V_BLANK_BIT_INDEX = 7
4
+ STATUS_SPRITE_HIT_BIT_INDEX = 5
5
+
6
+ # @param [Integer]
7
+ # @return [Integer]
8
+ attr_accessor :control1
9
+
10
+ # @param [Integer]
11
+ # @return [Integer]
12
+ attr_accessor :control2
13
+
14
+ # @param [Integer]
15
+ # @return [Integer]
16
+ attr_accessor :scroll_horizontal
17
+
18
+ # @param [Integer]
19
+ # @return [Integer]
20
+ attr_accessor :scroll_vertical
21
+
22
+ # @param [Integer]
23
+ # @return [Integer]
24
+ attr_accessor :status
25
+
26
+ def initialize
27
+ @control1 = 0x0
28
+ @control2 = 0x0
29
+ @scroll_horizontal = 0x0
30
+ @scroll_vertical = 0x0
31
+ @status = 0x0
32
+ end
33
+
34
+ # @return [Boolean]
35
+ def has_background_bank_bit?
36
+ @control1[4] == 1
37
+ end
38
+
39
+ # @return [Boolean]
40
+ def has_background_enabled_bit?
41
+ @control2[3] == 1
42
+ end
43
+
44
+ # @return [Boolean]
45
+ def has_in_v_blank_bit?
46
+ @status[STATUS_IN_V_BLANK_BIT_INDEX] == 1
47
+ end
48
+
49
+ # @return [Boolean]
50
+ def has_large_video_ram_address_offset_bit?
51
+ @control1[4] == 1
52
+ end
53
+
54
+ # @return [Boolean]
55
+ def has_sprite_enabled_bit?
56
+ @control2[4] == 1
57
+ end
58
+
59
+ # @return [Boolean]
60
+ def has_sprite_hit_bit?
61
+ @status[STATUS_SPRITE_HIT_BIT_INDEX] == 1
62
+ end
63
+
64
+ # @return [Boolean]
65
+ def has_sprite_bank_bit?
66
+ @control1[3] == 1
67
+ end
68
+
69
+ # @return [Boolean]
70
+ def has_v_blank_irq_enabled_bit?
71
+ @control1[7] == 1
72
+ end
73
+
74
+ # Name table id (address)
75
+ # +------------+------------|
76
+ # | 0 (0x2000) | 1 (0x2400) |
77
+ # +------------+------------|
78
+ # | 2 (0x2800) | 3 (0x2C00) |
79
+ # +------------+------------|
80
+ # @return [Integer] An integer from 0 to 3.
81
+ def name_table_id
82
+ @status & 0b11
83
+ end
84
+
85
+ def set_in_v_blank_bit
86
+ @status |= (1 << STATUS_IN_V_BLANK_BIT_INDEX)
87
+ end
88
+
89
+ # @param [Boolean] boolean
90
+ def toggle_in_v_blank_bit(boolean)
91
+ toggle_status_bit(STATUS_IN_V_BLANK_BIT_INDEX, boolean)
92
+ end
93
+
94
+ # @param [Boolean] boolean
95
+ def toggle_sprite_hit_bit(boolean)
96
+ toggle_status_bit(STATUS_SPRITE_HIT_BIT_INDEX, boolean)
97
+ end
98
+
99
+ private
100
+
101
+ # @param [Integer] index
102
+ # @param [Boolean] boolean
103
+ def toggle_status_bit(index, boolean)
104
+ if boolean
105
+ @status |= 1 << index
106
+ else
107
+ @status &= ~(1 << index)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,23 @@
1
+ module Rnes
2
+ class Ram
3
+ # @param [Integer] bytesize
4
+ def initialize(bytesize:)
5
+ @bytes = Array.new(bytesize).map do
6
+ 0
7
+ end
8
+ end
9
+
10
+ # @param [Integer] address
11
+ # @return [Integer]
12
+ def read(address)
13
+ @bytes[address]
14
+ end
15
+
16
+ # @param [Integer] address
17
+ # @param [Integer] value
18
+ # @return [Integer]
19
+ def write(address, value)
20
+ @bytes[address] = value
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Rnes
2
+ class Rom
3
+ # @param [Integer] bytes
4
+ def initialize(bytes:)
5
+ @bytes = bytes
6
+ end
7
+
8
+ # @return [Integer]
9
+ def bytesize
10
+ @bytes.length
11
+ end
12
+
13
+ # @param [Integer] address
14
+ # @param [Integer] value
15
+ def read(address)
16
+ @bytes[address]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ require 'rnes/errors'
2
+ require 'rnes/ines_header'
3
+ require 'rnes/rom'
4
+
5
+ module Rnes
6
+ class RomLoader
7
+ # @param [Array<Integer>] bytes
8
+ def initialize(bytes)
9
+ @bytes = bytes
10
+ end
11
+
12
+ # @return [Rnes::Rom]
13
+ # @raise [Rnes::Errors::InvalidInesFormatError]
14
+ def character_rom
15
+ validate!
16
+ ::Rnes::Rom.new(bytes: character_rom_bytes)
17
+ end
18
+
19
+ # @return [Rnes::Rom]
20
+ # @raise [Rnes::Errors::InvalidInesFormatError]
21
+ def program_rom
22
+ validate!
23
+ ::Rnes::Rom.new(bytes: program_rom_bytes)
24
+ end
25
+
26
+ # @return [Rnes::Rom]
27
+ # @raise [Rnes::Errors::InvalidInesFormatError]
28
+ def trainer_rom
29
+ validate!
30
+ ::Rnes::Rom.new(bytes: trainer_bytes)
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Array<Integer>]
36
+ def character_rom_bytes
37
+ @bytes.slice(character_rom_index, character_rom_bytesize)
38
+ end
39
+
40
+ # @return [Integer]
41
+ def character_rom_bytesize
42
+ ines_header.character_rom_bytesize
43
+ end
44
+
45
+ # @return [Integer]
46
+ def character_rom_index
47
+ program_rom_index + program_rom_bytesize
48
+ end
49
+
50
+ # @return [Rnes::InesHeader]
51
+ def ines_header
52
+ @ines_header ||= ::Rnes::InesHeader.new(@bytes)
53
+ end
54
+
55
+ # @return [Array<Integer>]
56
+ def program_rom_bytes
57
+ @bytes.slice(program_rom_index, program_rom_bytesize)
58
+ end
59
+
60
+ # @return [Integer]
61
+ def program_rom_bytesize
62
+ @program_rom_bytesize ||= ines_header.program_rom_bytesize
63
+ end
64
+
65
+ # @return [Integer]
66
+ def program_rom_index
67
+ @program_rom_index ||= trainer_index + trainer_bytesize
68
+ end
69
+
70
+ # @return [Array<Integer>]
71
+ def trainer_bytes
72
+ @bytes.slice(trainer_index, trainer_bytesize)
73
+ end
74
+
75
+ # @return [Integer]
76
+ def trainer_bytesize
77
+ ines_header.trainer_bytesize
78
+ end
79
+
80
+ # @return [Integer]
81
+ def trainer_index
82
+ ines_header.bytesize
83
+ end
84
+
85
+ # @return [Boolean]
86
+ def valid?
87
+ ines_header.valid?
88
+ end
89
+
90
+ # @raise [Rnes::Errors::InvalidInesFormatError]
91
+ def validate!
92
+ unless valid?
93
+ raise ::Rnes::Errors::InvalidInesFormatError
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,59 @@
1
+ module Rnes
2
+ class TerminalRenderer
3
+ BRAILLE_BASE_CODE_POINT = 0x2800
4
+
5
+ BRAILLE_HEIGHT = 4
6
+
7
+ BRAILLE_WIDTH = 2
8
+
9
+ BRIGHTNESS_SUM_THRESHOLD = 128 * 3
10
+
11
+ TEXT_HEIGHT = 61
12
+
13
+ TEXT_WIDTH = 128
14
+
15
+ ESCAPE_TO_CLEAR_TEXT = "\e[#{TEXT_HEIGHT}A\e[#{TEXT_WIDTH}D".freeze
16
+
17
+ def initialize
18
+ @fps = 0
19
+ @previous_fps = 0
20
+ end
21
+
22
+ def render(image)
23
+ brailles = convert_image_to_string(image)
24
+ fps_counter = "FPS:#{@previous_fps}"
25
+ puts "#{ESCAPE_TO_CLEAR_TEXT}#{fps_counter}\n#{brailles}"
26
+ second = ::Time.now.sec
27
+ if @second == second
28
+ @fps += 1
29
+ else
30
+ @previous_fps = @fps
31
+ @fps = 0
32
+ @second = second
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # @return [String]
39
+ def convert_image_to_string(image)
40
+ 0.step(image.height - 1, BRAILLE_HEIGHT).map do |y|
41
+ 0.step(image.width - 1, BRAILLE_WIDTH).map do |x|
42
+ offset = [
43
+ image.read(x: x + 0, y: y + 0),
44
+ image.read(x: x + 0, y: y + 1),
45
+ image.read(x: x + 0, y: y + 2),
46
+ image.read(x: x + 1, y: y + 0),
47
+ image.read(x: x + 1, y: y + 1),
48
+ image.read(x: x + 1, y: y + 2),
49
+ image.read(x: x + 0, y: y + 3),
50
+ image.read(x: x + 1, y: y + 3),
51
+ ].map.with_index do |rgb, i|
52
+ (rgb.sum < BRIGHTNESS_SUM_THRESHOLD ? 0 : 1) << i
53
+ end.reduce(:|)
54
+ (BRAILLE_BASE_CODE_POINT + offset).chr('UTF-8')
55
+ end.join
56
+ end.join("\n")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Rnes
2
+ VERSION = '0.1.0'.freeze
3
+ end