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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +28 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +59 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rnes +10 -0
- data/lib/rnes.rb +22 -0
- data/lib/rnes/cpu.rb +1058 -0
- data/lib/rnes/cpu_bus.rb +105 -0
- data/lib/rnes/cpu_registers.rb +151 -0
- data/lib/rnes/dma_controller.rb +35 -0
- data/lib/rnes/emulator.rb +61 -0
- data/lib/rnes/errors.rb +46 -0
- data/lib/rnes/image.rb +32 -0
- data/lib/rnes/ines_header.rb +89 -0
- data/lib/rnes/interrupt_line.rb +30 -0
- data/lib/rnes/keypad.rb +51 -0
- data/lib/rnes/logger.rb +142 -0
- data/lib/rnes/operation.rb +52 -0
- data/lib/rnes/operation/records.rb +1477 -0
- data/lib/rnes/parts_factory.rb +99 -0
- data/lib/rnes/ppu.rb +383 -0
- data/lib/rnes/ppu/colors.rb +70 -0
- data/lib/rnes/ppu_bus.rb +60 -0
- data/lib/rnes/ppu_registers.rb +111 -0
- data/lib/rnes/ram.rb +23 -0
- data/lib/rnes/rom.rb +19 -0
- data/lib/rnes/rom_loader.rb +97 -0
- data/lib/rnes/terminal_renderer.rb +59 -0
- data/lib/rnes/version.rb +3 -0
- data/rnes.gemspec +31 -0
- metadata +153 -0
@@ -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
|
data/lib/rnes/ppu_bus.rb
ADDED
@@ -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
|
data/lib/rnes/ram.rb
ADDED
@@ -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
|
data/lib/rnes/rom.rb
ADDED
@@ -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
|
data/lib/rnes/version.rb
ADDED