rnes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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