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,105 @@
1
+ require 'rnes/errors'
2
+
3
+ module Rnes
4
+ class CpuBus
5
+ # @param [Rnes::Rom]
6
+ # @return [Rnes::Rom]
7
+ attr_accessor :program_rom
8
+
9
+ # @param [Rnes::DmaController] dma_controller
10
+ # @param [Rnes::Keypad] keypad1
11
+ # @param [Rnes::Keypad] keypad2
12
+ # @param [Rnes::Ppu] ppu
13
+ # @param [Rnes::Ram] ram
14
+ def initialize(dma_controller:, keypad1:, keypad2:, ppu:, ram:)
15
+ @dma_controller = dma_controller
16
+ @keypad1 = keypad1
17
+ @keypad2 = keypad2
18
+ @ppu = ppu
19
+ @ram = ram
20
+ end
21
+
22
+ # @todo
23
+ # @param [Integer]
24
+ # @return [Integer]
25
+ def read(address)
26
+ case address
27
+ when 0x0000..0x07FF
28
+ @ram.read(address)
29
+ when 0x0800..0x1FFF
30
+ @ram.read(address - 0x0800)
31
+ when 0x2000..0x2007
32
+ @ppu.read(address - 0x2000)
33
+ when 0x2008..0x3FFF
34
+ @ppu.read(address - 0x2008)
35
+ when 0x4016
36
+ @keypad1.read
37
+ when 0x4017
38
+ @keypad2.read
39
+ when 0x4000..0x401F
40
+ 0 # TODO: I/O port for APU, etc
41
+ when 0x4020..0x5FFF
42
+ 0 # TODO: extended RAM on special mappers
43
+ when 0x6000..0x7FFF
44
+ 0 # TODO: battery-backed-up RAM
45
+ when 0x8000..0xBFFF
46
+ try_to_read_program_rom(address - 0x8000)
47
+ when 0xC000..0xFFFF
48
+ try_to_read_program_rom(address - offset_on_reading_program_rom_higher_region)
49
+ else
50
+ raise ::Rnes::Errors::InvalidCpuBusAddressError, address
51
+ end
52
+ end
53
+
54
+ # @todo
55
+ # @param [Integer] address
56
+ # @param [Integer] value
57
+ def write(address, value)
58
+ case address
59
+ when 0x0000..0x07FF
60
+ @ram.write(address, value)
61
+ when 0x0800..0x1FFF
62
+ @ram.write(address - 0x0800, value)
63
+ when 0x2000..0x2007
64
+ @ppu.write(address - 0x2000, value)
65
+ when 0x2008..0x3FFF
66
+ @ppu.write(address - 0x2008, value)
67
+ when 0x4014
68
+ @dma_controller.request_transfer(address_hint: value)
69
+ when 0x4016
70
+ @keypad1.write(value)
71
+ when 0x4017
72
+ @keypad2.write(value)
73
+ when 0x4000..0x401F
74
+ 0 # TODO: I/O port for APU, etc
75
+ when 0x4020..0x5FFF
76
+ 0 # TODO: extended RAM on special mappers
77
+ when 0x6000..0x7FFF
78
+ 0 # TODO: battery-backed-up RAM
79
+ else
80
+ raise ::Rnes::Errors::InvalidCpuBusAddressError, address
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ # @return [Boolean]
87
+ def attatched_to_large_program_rom?
88
+ @program_rom.bytesize > 16 * 2**10
89
+ end
90
+
91
+ # @return [Integer]
92
+ def offset_on_reading_program_rom_higher_region
93
+ attatched_to_large_program_rom? ? 0x8000 : 0xC000
94
+ end
95
+
96
+ # @param [Integer] address
97
+ def try_to_read_program_rom(address)
98
+ if @program_rom
99
+ @program_rom.read(address)
100
+ else
101
+ raise ::Rnes::Errors::ProgramRomNotConnectedError
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,151 @@
1
+ module Rnes
2
+ class CpuRegisters
3
+ CARRY_BIT_INDEX = 0
4
+ ZERO_BIT_INDEX = 1
5
+ INTERRUPT_BIT_INDEX = 2
6
+ DECIMAL_BIT_INDEX = 3
7
+ BREAK_BIT_INDEX = 4
8
+ RESERVED_BIT_INDEX = 5
9
+ OVERFLOW_BIT_INDEX = 6
10
+ NEGATIVE_BIT_INDEX = 7
11
+
12
+ # @param [Integer]
13
+ # @return [Integer]
14
+ attr_accessor :accumulator
15
+
16
+ # @param [Integer]
17
+ # @return [Integer]
18
+ attr_accessor :index_x
19
+
20
+ # @param [Integer]
21
+ # @return [Integer]
22
+ attr_accessor :index_y
23
+
24
+ # @param [Integer]
25
+ # @return [Integer]
26
+ attr_accessor :program_counter
27
+
28
+ # @param [Integer]
29
+ # @return [Integer]
30
+ attr_accessor :stack_pointer
31
+
32
+ # @param [Integer]
33
+ # @return [Integer]
34
+ attr_accessor :status
35
+
36
+ def initialize
37
+ @accumulator = 0x00
38
+ @index_x = 0x00
39
+ @index_y = 0x00
40
+ @program_counter = 0x0000
41
+ @stack_pointer = 0x0000
42
+ @status = 0b00000000
43
+ end
44
+
45
+ # @return [Boolean]
46
+ def break?
47
+ @status[BREAK_BIT_INDEX] == 1
48
+ end
49
+
50
+ # @param [Boolean] boolean
51
+ def break=(boolean)
52
+ toggle_bit(BREAK_BIT_INDEX, boolean)
53
+ end
54
+
55
+ # @return [Boolean]
56
+ def carry?
57
+ @status[CARRY_BIT_INDEX] == 1
58
+ end
59
+
60
+ # @param [Boolean] boolean
61
+ def carry=(boolean)
62
+ toggle_bit(CARRY_BIT_INDEX, boolean)
63
+ end
64
+
65
+ # @return [Integer]
66
+ def carry_bit
67
+ @status[CARRY_BIT_INDEX]
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def decimal?
72
+ @status[DECIMAL_BIT_INDEX] == 1
73
+ end
74
+
75
+ # @param [Boolean] boolean
76
+ def decimal=(boolean)
77
+ toggle_bit(DECIMAL_BIT_INDEX, boolean)
78
+ end
79
+
80
+ # @return [Boolean]
81
+ def interrupt?
82
+ @status[INTERRUPT_BIT_INDEX] == 1
83
+ end
84
+
85
+ # @param [Boolean] boolean
86
+ def interrupt=(boolean)
87
+ toggle_bit(INTERRUPT_BIT_INDEX, boolean)
88
+ end
89
+
90
+ # @return [Boolean]
91
+ def negative?
92
+ @status[NEGATIVE_BIT_INDEX] == 1
93
+ end
94
+
95
+ # @param [Boolean] boolean
96
+ def negative=(boolean)
97
+ toggle_bit(NEGATIVE_BIT_INDEX, boolean)
98
+ end
99
+
100
+ # @return [Boolean]
101
+ def overflow?
102
+ @status[OVERFLOW_BIT_INDEX] == 1
103
+ end
104
+
105
+ # @return [Boolean]
106
+ def reserved?
107
+ @status[RESERVED_BIT_INDEX] == 1
108
+ end
109
+
110
+ # @param [Boolean] boolean
111
+ def reserved=(boolean)
112
+ toggle_bit(RESERVED_BIT_INDEX, boolean)
113
+ end
114
+
115
+ def reset
116
+ @accumulator = 0x00
117
+ @index_x = 0x00
118
+ @index_y = 0x00
119
+ @program_counter = 0x0000
120
+ @stack_pointer = 0x1FD
121
+ @status = 0b00110100
122
+ end
123
+
124
+ # @param [Boolean] boolean
125
+ def overflow=(boolean)
126
+ toggle_bit(OVERFLOW_BIT_INDEX, boolean)
127
+ end
128
+
129
+ # @return [Boolean]
130
+ def zero?
131
+ @status[ZERO_BIT_INDEX] == 1
132
+ end
133
+
134
+ # @param [Boolean] boolean
135
+ def zero=(boolean)
136
+ toggle_bit(ZERO_BIT_INDEX, boolean)
137
+ end
138
+
139
+ private
140
+
141
+ # @param [Integer] index
142
+ # @param [Boolean] boolean
143
+ def toggle_bit(index, boolean)
144
+ if boolean
145
+ @status |= 1 << index
146
+ else
147
+ @status &= ~(1 << index)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,35 @@
1
+ module Rnes
2
+ class DmaController
3
+ TRANSFER_BYTESIZE = 2**8
4
+
5
+ # @param [Rnes::Ppu] ppu
6
+ # @param [Rnes::Ram] working_ram
7
+ def initialize(ppu:, working_ram:)
8
+ @ppu = ppu
9
+ @requested = false
10
+ @working_ram = working_ram
11
+ end
12
+
13
+ def transfer_if_requested
14
+ if @requested
15
+ transfer
16
+ end
17
+ end
18
+
19
+ # @param [Integer] address_hint
20
+ def request_transfer(address_hint:)
21
+ @requested = true
22
+ @working_ram_address = address_hint << 8
23
+ end
24
+
25
+ private
26
+
27
+ def transfer
28
+ TRANSFER_BYTESIZE.times do |index|
29
+ value = @working_ram.read(@working_ram_address + index)
30
+ @ppu.transfer_sprite_data(index: index, value: value)
31
+ end
32
+ @requested = false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ require 'io/console'
2
+ require 'rnes/logger'
3
+ require 'rnes/parts_factory'
4
+ require 'rnes/rom_loader'
5
+
6
+ module Rnes
7
+ class Emulator
8
+ LOG_FILE_NAME = 'rnes.log'.freeze
9
+
10
+ def initialize
11
+ parts_factory = ::Rnes::PartsFactory.new
12
+ @cpu = parts_factory.cpu
13
+ @cpu_bus = parts_factory.cpu_bus
14
+ @dma_controller = parts_factory.dma_controller
15
+ @keypad1 = parts_factory.keypad1
16
+ @keypad2 = parts_factory.keypad2
17
+ @ppu = parts_factory.ppu
18
+ @ppu_bus = parts_factory.ppu_bus
19
+ @logger = ::Rnes::Logger.new(cpu: @cpu, path: LOG_FILE_NAME, ppu: @ppu)
20
+ end
21
+
22
+ # @param [Array<Integer>] rom_bytes
23
+ def load_rom(rom_bytes)
24
+ rom_loader = ::Rnes::RomLoader.new(rom_bytes)
25
+ copy(from: rom_loader.character_rom, to: @ppu_bus.character_ram)
26
+ @cpu_bus.program_rom = rom_loader.program_rom
27
+ @cpu.reset
28
+ end
29
+
30
+ def run
31
+ $stdin.noecho do
32
+ loop do
33
+ if @logger
34
+ @logger.puts
35
+ end
36
+ tick
37
+ end
38
+ end
39
+ end
40
+
41
+ def tick
42
+ @dma_controller.transfer_if_requested
43
+ (@cpu.tick * 3).times do
44
+ @ppu.tick
45
+ end
46
+ @keypad1.check
47
+ @keypad2.check
48
+ end
49
+
50
+ private
51
+
52
+ # @param [Rnes::Rom] from
53
+ # @param [Rnes::Ram] to
54
+ def copy(from:, to:)
55
+ from.bytesize.times do |address|
56
+ value = from.read(address)
57
+ to.write(address, value)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,46 @@
1
+ module Rnes
2
+ module Errors
3
+ class BaseError < ::StandardError
4
+ end
5
+
6
+ class BaseInvalidAddressError < BaseError
7
+ # @param [Integer] address
8
+ def initialize(address)
9
+ @address = address
10
+ super(to_s)
11
+ end
12
+
13
+ # @return [String]
14
+ def to_s
15
+ format('Invalid address: 0x%04X', @address)
16
+ end
17
+ end
18
+
19
+ class InvalidAddressingModeError < BaseError
20
+ end
21
+
22
+ class InvalidCpuBusAddressError < BaseInvalidAddressError
23
+ end
24
+
25
+ class InvalidInesFormatError < BaseError
26
+ end
27
+
28
+ class InvalidOperationCodeError < BaseError
29
+ end
30
+
31
+ class InvalidOperationError < BaseError
32
+ end
33
+
34
+ class InvalidPpuAddressError < BaseInvalidAddressError
35
+ end
36
+
37
+ class InvalidPpuBusAddressError < BaseInvalidAddressError
38
+ end
39
+
40
+ class ProgramRomNotConnectedError < BaseError
41
+ end
42
+
43
+ class StackPointerOverflowError < BaseError
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ module Rnes
2
+ class Image
3
+ # @return [Integer]
4
+ attr_reader :height
5
+
6
+ # @return [Integer]
7
+ attr_reader :width
8
+
9
+ # @param [Integer] height
10
+ # @param [Integer] width
11
+ def initialize(height:, width:)
12
+ @bytes = Array.new(height * width) do
13
+ [0, 0, 0]
14
+ end
15
+ @height = height
16
+ @width = width
17
+ end
18
+
19
+ # @param [Integer] x
20
+ # @param [Integer] y
21
+ def read(x:, y:)
22
+ @bytes[@width * y + x]
23
+ end
24
+
25
+ # @param [Array<Integer>] rgb
26
+ # @param [Integer] x
27
+ # @param [Integer] y
28
+ def write(value:, x:, y:)
29
+ @bytes[@width * y + x] = value
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ module Rnes
2
+ class InesHeader
3
+ BYTESIZE = 16
4
+
5
+ PREFIX_BYTES = [
6
+ 0x4E, # N
7
+ 0x45, # E
8
+ 0x53, # S
9
+ 0x1A, # end-of-file in MS-DOS
10
+ ].freeze
11
+
12
+ # @param [Array<Integer>] bytes
13
+ def initialize(bytes)
14
+ @bytes = bytes
15
+ end
16
+
17
+ # @return [Integer]
18
+ def bytesize
19
+ BYTESIZE
20
+ end
21
+
22
+ # @return [Integer]
23
+ def character_ram_bytesize
24
+ @bytes[8]
25
+ end
26
+
27
+ # @return [Integer]
28
+ def character_rom_bytesize
29
+ @bytes[5] * 8 * 2**10
30
+ end
31
+
32
+ # @return [Boolean]
33
+ def has_battery_backed_program_rom_bit?
34
+ flags1[1] == 1
35
+ end
36
+
37
+ # @return [Boolean]
38
+ def has_mirror_ignoring_bit?
39
+ flags1[3] == 1
40
+ end
41
+
42
+ # @note Trainers are 512 bytes of code which is loaded into $7000 before the game starts for hacker use.
43
+ # @return [Boolean]
44
+ def has_trainer_bit?
45
+ flags1[2] == 1
46
+ end
47
+
48
+ # @return [Boolean]
49
+ def has_vertical_mirroring_bit?
50
+ flags1[0] == 1
51
+ end
52
+
53
+ # @return [Integer]
54
+ def mapper_number
55
+ flags2 & 0b11110000 | flags1 >> 4
56
+ end
57
+
58
+ # @return [Integer]
59
+ def program_rom_bytesize
60
+ @bytes[4] * 16 * 2**10
61
+ end
62
+
63
+ # @return [Integer]
64
+ def trainer_bytesize
65
+ if has_trainer_bit?
66
+ 512
67
+ else
68
+ 0
69
+ end
70
+ end
71
+
72
+ # @return [Boolean]
73
+ def valid?
74
+ @bytes[0..3] == PREFIX_BYTES
75
+ end
76
+
77
+ private
78
+
79
+ # @return [Integer]
80
+ def flags1
81
+ @bytes[6]
82
+ end
83
+
84
+ # @return [Integer]
85
+ def flags2
86
+ @bytes[7]
87
+ end
88
+ end
89
+ end