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